动态规划(Dynamic Programming)
动态规划问题参考算法导论第15章,动态规划的核心思想在于拆分子问题,记住过往,减少重复计算。可以将动态规划理解为有限状态自动机或者有向无环图,每一个节点代表一个状态,任何一个非起始节点都可以从其他节点转移过来称为状态转移。当前求解的过程中参考了以前求解过的问题的结果,这个过程不能形成回路,形成回路就无法求解。
动态规划求解步骤:
1. 设计状态,确定状态含义
2. 确定状态转移方程
3. 确定初始状态
4. 执行状态转移
5. 计算最终的解
常见题型:
- 0070-爬楼梯
- 1342-将数字变成 0 的操作次数
- 0746-使用最小花费爬楼梯
- 0198-打家劫舍
- 0213-打家劫舍 II
- 0091-解码方法
- 1646-获取生成数组中的最大值
- 1043-分隔数组以得到最大和
- 139-单词拆分
- 1869-哪种连续子字符串更长
- 724-寻找数组的中心下标
- 221-最大正方形
- 1277-统计全为 1 的正方形子矩阵
- 256-粉刷房子 LCR 091. 粉刷房子
- 53-最大子数组和
- 300-最长递增子序列
- 673-最长递增子序列的个数
- 1027-最长等差数列
- 873-最长的斐波那契子序列的长度
- 516-最长回文子序列
- 1143-最长公共子序列
- 72-编辑距离
- 118-杨辉三角
- 62-不同路径
- 64-最小路径和
- 121-买卖股票的最佳时机
- 122-买卖股票的最佳时机 II
- 123-买卖股票的最佳时机 III
- 188-买卖股票的最佳时机 IV
- 309-买卖股票的最佳时机含冷冻期
- 714-买卖股票的最佳时机含手续费
- 416-分割等和子集
青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 10 级的台阶总共有多少种跳法。相同问题:70-爬楼梯 通用公式为: f(n) = f(n-1) + f(n-2) 当只有2级台阶时,有两种跳法,第一种是直接跳两级,第二种是先跳一级,然后再跳一级。即f(2) = 2; 当只有1级台阶时,只有一种跳法,即f(1) = 1; 描述为有向无环图如下所示:

若使用递归方法实现,将由很多重复节点计算:
int numWays(int n)
{
if(n == 1){return 1;}
if(n == 2){return 2;}
return numWays(n-1) + numWays(n-2);
}
使用动态规划实现,减少重复计算:
int numWays(int n)
{
if (n <= 1) {return 1;}
if (n == 2) {return 2;}
int a = 1;
int b = 2;
int c = 0;
for (int i = 3; i <= n; i++) {
c = (a + b)% 1000000007;
a = b;
b = c;
}
return c;
}
70-爬楼梯:
int climbStairs(int n)
{
if(n <= 2){return n;}
// f(n) = f(n - 1) + f(n - 2)
int a = 1, b = 2, c = 0;
for(int pos = 2; pos < n; pos ++){
c = a + b;
a = b;b = c;
}
return c;
}
贪心算法(Greedy Algorithm)
贪心算法参考算法导论第16章,
- 300-最长递增子序列
常见问题解析
股票问题:
参考 https://leetcode.cn/circle/discuss/qiAgHn/
121. 买卖股票的最佳时机
122. 买卖股票的最佳时机 II
123. 买卖股票的最佳时机 III
188. 买卖股票的最佳时机 IV
309. 最佳买卖股票时机含冷冻期
714. 买卖股票的最佳时机含手续费
符号: * 用 n 表示股票价格数组的长度; * 用 i 表示第 i 天(i 的取值范围是 0 到 n - 1); * 用 k 表示允许的最大交易次数; * 用 T[i][k] 表示在第 i 天结束时,最多进行 k 次交易的情况下可以获得的最大收益。
基准情况是显而易见的:T[-1][k] = T[i][0] = 0,表示没有进行股票交易时没有收益(注意第一天对应 i = 0,因此 i = -1 表示没有股票交易)。
基准情况:
T[-1][k][0] = 0, T[-1][k][1] = -Infinity
T[i][0][0] = 0, T[i][0][1] = -Infinity
状态转移方程:
T[i][k][0] = max(T[i - 1][k][0], T[i - 1][k][1] + prices[i])
T[i][k][1] = max(T[i - 1][k][1], T[i - 1][k - 1][0] - prices[i])
情况一:k = 1
T[i][1][0] = max(T[i - 1][1][0], T[i - 1][1][1] + prices[i])
T[i][1][1] = max(T[i - 1][1][1], T[i - 1][0][0] - prices[i]) = max(T[i - 1][1][1], -prices[i])
第二个状态转移方程利用了 T[i][0][0] = 0。
情况二:k 为正无穷
如果 k 为正无穷,则 k 和 k - 1 可以看成是相同的,因此有 T[i - 1][k - 1][0] = T[i - 1][k][0] 和 T[i - 1][k - 1][1] = T[i - 1][k][1]。每天仍有两个未知变量:T[i][k][0] 和 T[i][k][1],其中 k 为正无穷,状态转移方程如下:
T[i][k][0] = max(T[i - 1][k][0], T[i - 1][k][1] + prices[i])
T[i][k][1] = max(T[i - 1][k][1], T[i - 1][k - 1][0] - prices[i]) = max(T[i - 1][k][1], T[i - 1][k][0] - prices[i])
情况三:k = 2
情况三和情况一相似,区别之处是,对于情况三,每天有四个未知变量:T[i][1][0]、T[i][1][1]、T[i][2][0]、T[i][2][1],状态转移方程如下:
T[i][2][0] = max(T[i - 1][2][0], T[i - 1][2][1] + prices[i])
T[i][2][1] = max(T[i - 1][2][1], T[i - 1][1][0] - prices[i])
T[i][1][0] = max(T[i - 1][1][0], T[i - 1][1][1] + prices[i])
T[i][1][1] = max(T[i - 1][1][1], T[i - 1][0][0] - prices[i]) = max(T[i - 1][1][1], -prices[i])
第四个状态转移方程利用了 T[i][0][0] = 0。
情况四:k 为任意值
问题等价于情况二。
情况五:k 为正无穷但有冷却时间
但是在有「冷却时间」的情况下,如果在第 i - 1 天卖出了股票,就不能在第 i 天买入股票。因此,如果要在第 i 天买入股票,第二个状态转移方程中就不能使用 T[i - 1][k][0],而应该使用 T[i - 2][k][0]。状态转移方程中的别的项保持不变,新的状态转移方程如下:
T[i][k][0] = max(T[i - 1][k][0], T[i - 1][k][1] + prices[i])
T[i][k][1] = max(T[i - 1][k][1], T[i - 2][k][0] - prices[i])
情况六:k 为正无穷但有手续费
由于需要对每次交易付手续费,因此在每次买入或卖出股票之后的收益需要扣除手续费,新的状态转移方程有两种表示方法。
第一种表示方法,在每次买入股票时扣除手续费:
T[i][k][0] = max(T[i - 1][k][0], T[i - 1][k][1] + prices[i])
T[i][k][1] = max(T[i - 1][k][1], T[i - 1][k][0] - prices[i] - fee)
第二种表示方法,在每次卖出股票时扣除手续费:
T[i][k][0] = max(T[i - 1][k][0], T[i - 1][k][1] + prices[i] - fee)
T[i][k][1] = max(T[i - 1][k][1], T[i - 1][k][0] - prices[i])
状态转移理解:
对于状态转移方程中的 T[i][k][0],第 i 天进行的操作只能是休息或卖出,因为在第 i 天结束时持有的股票数量是 0。T[i - 1][k][0] 是休息操作可以得到的最大收益,T[i - 1][k][1] + prices[i] 是卖出操作可以得到的最大收益。注意到允许的最大交易次数是不变的,因为每次交易包含两次成对的操作,买入和卖出。只有买入操作会改变允许的最大交易次数。
对于状态转移方程中的 T[i][k][1],第 i 天进行的操作只能是休息或买入,因为在第 i 天结束时持有的股票数量是 1。T[i - 1][k][1] 是休息操作可以得到的最大收益,T[i - 1][k - 1][0] - prices[i] 是买入操作可以得到的最大收益。注意到允许的最大交易次数减少了一次,因为每次买入操作会使用一次交易。
为了得到最后一天结束时的最大收益,可以遍历股票价格数组,根据状态转移方程计算 T[i][k][0] 和 T[i][k][1] 的值。最终答案是 T[n - 1][k][0],因为结束时持有 0 份股票的收益一定大于持有 1 份股票的收益。
前缀和
最长递增子序列
https://writings.sh/post/longest-increasing-subsequence-revisited
斐波那契数列
https://leetcode.cn/problems/fibonacci-number/solutions/545049/fei-bo-na-qi-shu-by-leetcode-solution-o4ze/
Reference
[0] https://github.com/krahets/hello-algo
[1] https://www.hello-algo.com/
[2] https://www.programmercarl.com/
[3] https://leetcode.cn/problem-list/2cktkvj/