算法题解
DP核心思想
重叠子问题
可以使用"备忘录"和"DPTable"来记录中间结果
最优子结构
可以从子问题的最优结果推出更大规模问题的最优结果。让你算每个班的最优成绩就是子问题,你知道所有子问题的答案后,就可以借此推出全校学生的最优成绩这个规模更大的问题的答案。状态转移方程
三要素框架
明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。
# 初始化 base case
dp[0][0][...] = base
# 进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 求最值(选择1,选择2...)Demo
比如计算最长递增子序列,需要定义好dp数组,然后只要知道dp[i]=x这个含义是什么,在最长递增子序列里:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。
股票最佳买卖时机问题
根据状态去做穷举,每天分为三种选择:买入、卖出、无操作,其余就是三种状态的组合并且根据交易次数限制
而且我们可以用自然语言描述出每一个状态的含义,比如说
dp[3][2][1]的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如dp[2][3][0]的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。很容易理解,对吧?我们想求的最终答案是dp[n-1] [k] [0],即最后一天,最多允许 K 次交易,最多获得多少利润。读者可能问为什么不是 dp[n - 1] [K] [1]?因为 [1] 代表手上还持有股票,[0] 表示手上的股票已经卖出去了,很显然后者得到的利润一定大于前者。
状态转移方程
base case
总结
0-1背包问题
题目
给你一个可装载重量为
W的背包和N个物品,每个物品有重量和价值两个属性。其中第i个物品的重量为wt[i],价值为val[i],现在让你用这个背包装物品,最多能装的价值是多少?
确定""状态和"选择"
先说状态,如何才能描述一个问题局面?只要给定几个可选物品和一个背包的容量限制,就形成了一个背包问题,对不对?所以状态有两个,就是「背包的容量」和「可选择的物品」。
再说选择,也很容易想到啊,对于每件物品,你能选择什么?选择就是「装进背包」或者「不装进背包」嘛。
明确dp数组的定义
dp数组是什么?其实就是描述问题局面的一个数组。换句话说,我们刚才明确问题有什么「状态」,现在需要用dp数组把状态表示出来。状态有两个所以可以用一个二维数组来表示,
dp[i][w]定义如下:前i个物品,当前背包的容量为w,这种情况可以装的最大价值就是dp[i][w]。比如说,如果 dp[3] [5] = 6,其含义为:对于给定的一系列物品中,若只对前 3 个物品进行选择,当背包容量为 5 时,最多可以装下的价值为 6。
base case
dp[0][..]=dp[...][0]=0最终得到的0-1背包问题框架
状态转移方程
dp[i][w]表示:对于前i个物品,当前背包的容量为w时,这种情况下可以装下的最大价值是dp[i][w]。
如果你没有把这第i个物品装入背包,那么很显然,最大价值dp[i][w]应该等于dp[i-1][w]。你不装嘛,那就继承之前的结果。
如果你把这第i个物品装入了背包,那么dp[i][w]应该等于dp[i-1][w-wt[i-1]] + val[i-1]。
首先,由于i是从 1 开始的,所以对val和wt的取值是i-1。
而dp[i-1][w-wt[i-1]]也很好理解:你如果想装第i个物品,你怎么计算这时候的最大价值?换句话说,在装第i个物品的前提下,背包能装的最大价值是多少?
显然,你应该寻求剩余重量w-wt[i-1]限制下能装的最大价值,加上第i个物品的价值val[i-1],这就是装第i个物品的前提下,背包可以装的最大价值。
完全背包问题
有一个背包,最大容量为
amount,有一系列物品coins,每个物品的重量为coins[i],每个物品的数量无限。请问有多少种方法,能够把背包恰好装满?
编辑距离问题
题目
解题思路
使用双指针方式,从
s1和s2两字符串尾部开始移动,然后对字符串的内容做比较,如果相同则跳过,不同则添加或者删除操作,如果s1指针走完后,s2指针还没走完的话,将s2剩余的字符串全部放入s1头部。
base case
指针
i走完s1或者指针j走完s2
核心框架
打家劫舍系列问题
题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
状态和选择
偷的钱 偷或者不偷
base case
int [] dp=new int[N+2];
状态转移方程
子序列解题模板:最长回文子序列
俩种思路
第一种思路模板是一个一维的 dp 数组:
在子数组
array[0..i]中,以*array[i]*结尾的目标子序列(最长递增子序列)的长度是dp[i]。第二种思路模板是一个二维的 dp 数组:
题目
给定一个字符串
s,找到s中最长的回文子串。你可以假设s的最大长度为 1000。
贪心算法
调度区间算法
博弈问题
定义dp数组的含义
dp[start][end]其中start和end定义的选择数组的范围
状态转移方程
状态:开始的索引start,结束的索引end,当前轮到的人
选择:要么选择左边的石头,或者选择最右边的石头
base case
算法框架
KMP(Knuth-Morris-Pratt)字符匹配算法
算法概述
KMP 算法永不回退
txt的指针i****,不走回头路(不会重复扫描txt****),而是借助dp数组中储存的信息把pat移到正确的位置继续匹配,时间复杂度只需 O(N),用空间换时间。
定义状态
算法框架
例子
回溯(DFS)算法核心思想
解决一个回溯问题,实际上就是一个决策树的遍历过程。
三个问题
路径:也就是已经做出的选择。
选择列表:也就是你当前可以做的选择。
结束条件:也就是到达决策树底层,无法再做选择的条件。
算法框架

BFS算法核心思想
BFS 相对 DFS 的最主要的区别是:BFS 找到的路径一定是最短的,但代价就是空间复杂度比 DFS 大很多。
算法框架
为什么 BFS 可以找到最短距离,DFS 不行吗?
既然 BFS 那么好,为啥 DFS 还要存在?
双向BFS优化
传统的 BFS 框架就是从起点开始向四周扩散,遇到终点时停止;而双向 BFS 则是从起点和终点同时开始扩散,当两边有交集的时候停止。


图示中的树形结构,如果终点在最底部,按照传统 BFS 算法的策略,会把整棵树的节点都搜索一遍,最后找到
target;而双向 BFS 其实只遍历了半棵树就出现了交集,也就是找到了最短距离。从这个例子可以直观地感受到,双向 BFS 是要比传统 BFS 高效的。不过,双向 BFS 也有局限,因为你必须知道终点在哪里。比如我们刚才讨论的二叉树最小高度的问题,你一开始根本就不知道终点在哪里,也就无法使用双向 BFS;但是第二个密码锁的问题,是可以使用双向 BFS 算法来提高效率的,代码稍加修改即可:
二分搜索
二分搜索套路

滑动窗口算法
算法框架
https://leetcode-cn.com/problems/minimum-window-substring/solution/tong-su-qie-xiang-xi-de-miao-shu-hua-dong-chuang-k/
Union-Find算法
连通的特性
这里所说的「连通」是一种等价关系,也就是说具有如下三个性质:
1、自反性:节点p和p是连通的。
2、对称性:如果节点p和q连通,那么q和p也连通。
3、传递性:如果节点p和q连通,q和r连通,那么p和r也连通。
算法核心点
1、用 parent 数组记录每个节点的父节点,相当于指向父节点的指针,所以 parent 数组内实际存储着一个森林(若干棵多叉树)。
2、用 size 数组记录着每棵树的重量,目的是让 union 后树依然拥有平衡性,而不会退化成链表,影响操作效率。
3、在 find 函数中进行路径压缩,保证任意树的高度保持在常数,使得 union 和 connected API 时间复杂度为 O(1)。
代码实现
单调栈
算法框架模版
循环队列模版
双索引技术
二分查找法
滑动窗口
Leetcode3/76/209/438/567
查找问题
使用查找表,定义好map需要查找的键值对
链表相关
虚拟头节点
反转链表
队列和栈
栈
利用栈校验括号的合法性
队列
层序遍历相关
优先队列TopK
最后更新于