代码随想录—动态规划篇总结
文章目录
- 代码随想录---动态规划篇总结
- 动态规划的思路:
- 一、509. 斐波那契数
- 二、70. 爬楼梯
- 三、746. 使用最小花费爬楼梯
- 四、62. 不同路径
- 五、63. 不同路径 II
- 六、343. 整数拆分
- 七、不同的二叉搜索树
- 八、01背包理论基础
- 九、416. 分割等和子集
- 十、1049. 最后一块石头的重量 II
- 十一、494. 目标和
- 十二、474. 一和零
- 十三、完全背包理论基础
- 十四、518. 零钱兑换 II --- 组合问题
- 十五、377. 组合总和 Ⅳ --- 排列问题
- 十六、57. 爬楼梯(第八期模拟笔试) --- 排列问题
- 十七、322. 零钱兑换
- 十八、279. 完全平方数
- 十九、139. 单词拆分
- 二十、56. 携带矿石资源(第八期模拟笔试)
- 二十一、198. 打家劫舍
- 二十二、213. 打家劫舍 II
- 二十三、337. 打家劫舍 III
- 二十四、121. 买卖股票的最佳时机
- 二十五、122. 买卖股票的最佳时机 II
- 二十六、123. 买卖股票的最佳时机 III
- 二十七、188. 买卖股票的最佳时机 IV
- 二十八、309. 买卖股票的最佳时机含冷冻期
- 二十九、714. 买卖股票的最佳时机含手续费
- 三十、718. 最长重复子数组
- 三十一、1143. 最长公共子序列
- 三十二、718. 最长重复子数组
- 三十三、1143. 最长公共子序列
- 三十四、1035. 不相交的线
- 三十五、53. 最大子数组和
- 三十六、392. 判断子序列
- 三十七、115. 不同的子序列
- 三十八、583. 两个字符串的删除操作
- 三十九、72. 编辑距离
- 四十、647. 回文子串
- 四十一、516. 最长回文子序列
- 总结
动态规划的思路:
1.确定dp数组(dp table)以及下标的含义
2.确定递推公式
3.dp数组如何初始化
4.确定遍历顺序
5.举例推导dp数组
一、509. 斐波那契数
int fib(int n) {
if (n <= 1) return n;
//1.dp[i]:F(i)
vector<int> dp(n + 1, 0);
//2.状态转移方程:dp[i] = dp[i - 1] + dp[i - 2];
//3.初始化: dp[0] = F(0) = 0, dp[1] = F(1) = 1;
dp[0] = 0, dp[1] = 1;
//4.遍历顺序
for (int i = 2; i <= n; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
二、70. 爬楼梯
int climbStairs(int n) {
if (n <= 1) return n;
//1.dp[i]:爬i阶楼梯不同的方法数
vector<int> dp(n + 1, 0);
//2.状态转移方程:dp[i] = dp[i - 1] + dp[i - 2];
//3.初始化:
dp[0] = 1, dp[1] = 1;
//4.遍历顺序
for (int i = 2; i <= n; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
三、746. 使用最小花费爬楼梯
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
//1.dp[i]:爬上第i阶台阶的最低花费
vector<int> dp(n + 1, 0);
//2.状态转移方程:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
//3.初始化:
dp[0] = dp[1] = 0;
//4.遍历顺序
for (int i = 2; i <= n; i++)
{
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[n];
}
四、62. 不同路径
int uniquePaths(int m, int n) {
//1.dp[i][j]:到达第i行第j列的网格的路径数
vector<vector<int>> dp(m, vector<int>(n, 0));
//2.状态转移方程:dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
//3.初始化:机器人一直向下或者向右,只有一条路径
for (int i = 0; i < m; i++) dp[i][0] = 1;
for (int j = 0; j < n; j++) dp[0][j] = 1;
//4.遍历顺序
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
五、63. 不同路径 II
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//1.dp[i][j]:到达第i行第j列的网格的路径数
int n = obstacleGrid[0].size();
int m = obstacleGrid.size();
vector<vector<int>> dp(m, vector<int>(n, 0));
//2.状态转移方程:if(obstacleGrid[i][j] == 0) dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
//3.初始化:机器人一直向下或者向右,只有一条路径,并且障碍物无法到达,故初始化为0
for (int i = 0; i < m && obstacleGrid[i][0] != 1; i++) dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] != 1; j++) dp[0][j] = 1;
//4.遍历顺序
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
if(obstacleGrid[i][j] == 0) dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
六、343. 整数拆分
int integerBreak(int n) {
if (n <= 1) return n;
//1.dp[i]:正整数i可以拆分成整数的乘积最大
vector<int> dp(n + 1, 0);
//(i - j) * j 代表可以拆成两个整数
//max(dp[i - j] * j:代表可以拆成两个以上的整数
//2.状态转移方程:dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
//3.初始化:0的最大乘积是0, 1的最大乘积是1
dp[0] = 0, dp[1] = 1;
//4.遍历顺序 j只需要到i / 2,因为dp[j] * d[i - j] == dp[i - j] * dp[j];
for (int i = 2; i <= n; i++)
{
for (int j = 0; j <= i / 2; j++)
{
dp[i] = max(dp[i], max(dp[i - j] * j, (i - j) * j));
}
}
return dp[n];
}
七、不同的二叉搜索树
int numTrees(int n) {
//1.dp[i]:由i个节点组成的二叉搜索树的数量
vector<int> dp(n + 1, 0);
//2.状态转移方程:dp[i] += dp[j] * dp[i - j - 1];
//3.初始化:对于0个节点,有1个二叉搜索树
dp[0] = 1;
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
{
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
八、01背包理论基础
1.二维数组表示
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, m;
cin >> m >> n;
vector<int> weight(m, 0), value(m, 0);
for (int i = 0; i < m; i++) cin >> weight[i];
for (int i = 0; i < m; i++) cin >> value[i];
//1.dp[i][j]:前i个物品所占的空间为j时的最大价值
vector<vector<int>> dp(m, vector<int>(n + 1, 0));
//2.状态转移方程:dp[i][j] = dp[i - 1][j];
//if (j >= nums[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - weight[i]] + value[i]);
//3.初始化:初始化dp[0][0..n]
for (int j = 0; j <= n; j++) if (j >= weight[0]) dp[0][j] = value[0];
//4.遍历顺序
for (int i = 1; i < m; i++)
{
for (int j = 0; j <= n; j++)
{
dp[i][j] = dp[i - 1][j];
if (j >= weight[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[m - 1][n] << endl;
return 0;
}
2.一维数组表示
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, m;
cin >> m >> n;
vector<int> weight(m, 0), value(m, 0);
for (int i = 0; i < m; i++) cin >> weight[i];
for (int i = 0; i < m; i++) cin >> value[i];
//1.dp[j]:所占的空间为j时的最大价值
vector<int> dp(n + 1, 0);
//2.状态转移方程:if (j >= nums[i]) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
//3.初始化:无需初始化
//4.遍历顺序:i从0->m - 1, j为了防止覆盖,需要从n -> 0;
for (int i = 0; i < m; i++)
{
for (int j = n; j >= weight[i]; j--)
{
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[n] << endl;
return 0;
}
九、416. 分割等和子集
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) sum += nums[i];
if (sum % 2) return false;
int target = sum / 2;
//1.dp[j]:要求的值为j时可以组成的最大值为dp[j];
vector<int> dp(target + 1, 0);
//2.状态转移方程: if (j >= nums[i]) dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
//3.初始化:无需初始化
//4.遍历顺序:i从0->nums.size() - 1, j为了防止覆盖,需要从target -> 0;
for (int i = 0; i < nums.size(); i++)
{
for (int j = target; j >= nums[i]; j--)
{
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
十、1049. 最后一块石头的重量 II
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int n = stones.size();
int sum = 0;
for (int i = 0; i < n; i++) sum += stones[i];
int target = sum / 2;
//1.dp[i][j]:前i个石头且重量要求为j时可以装的最大重量
vector<vector<int>> dp(n, vector<int>(target + 1, 0));
//2.状态转移方程:dp[i][j] = dp[i - 1][j];
//else dp[i][j] = max(dp[i][j], dp[i - 1][j - stones[i]] + stones[i]);
//3.初始化:
for (int j = 0; j <= target; j++) if (j >= stones[0]) dp[0][j] = stones[0];
//4.遍历顺序
for (int i = 1; i < n; i++)
{
for (int j = 0; j <= target; j++)
{
dp[i][j] = dp[i - 1][j];
if (j >= stones[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - stones[i]] + stones[i]);
}
}
return (sum - dp[n - 1][target] - dp[n - 1][target]);
}
};
十一、494. 目标和
//组合问题
int findTargetSumWays(vector<int>& nums, int target) {
int n = nums.size();
int sum = 0;
for (int i = 0; i < n; i++) sum += nums[i];
if (abs(target) > sum) return 0;
if ((target + sum) % 2) return 0;
int bagSize = (target + sum) / 2;
//1.dp[j]:重量为j的背包可以装下的组合数目
vector<vector<int>> dp(n, vector<int>(bagSize + 1, 0));
//2.状态转移方程:dp[i][j] = d[i - 1][j]; //不考虑第i件物品的方法
//if (j >= nums[i]) dp[i][j] += dp[i - 1][j - nums[i]]; //考虑第i件物品的方法
//3.初始化:只考虑第0个物品,只有当物品可以完全装满背包时算一种方法
for (int j = 0; j <= bagSize; j++) if (j == nums[0]) dp[0][j] = 1;
//当背包容量为0时,不装物品也算一种方法,但是如果出现0,那么方法数量就是2 ^ (0的个数)
int zeroNum = 0;
for (int i = 0; i < n; i++)
{
if (nums[i] == 0) zeroNum++;
dp[i][0] = pow(2.0, zeroNum);
}
//4.遍历顺序
for (int i = 1; i < n; i++)
{
for (int j = 1; j <= bagSize; j++)
{
dp[i][j] = dp[i - 1][j];
if (j >= nums[i]) dp[i][j] += dp[i - 1][j - nums[i]];
}
}
return dp[n - 1][bagSize];
}
十二、474. 一和零
int findMaxForm(vector<string>& strs, int m, int n) {
//1.dp[i][j]:i个0j个1可以组成的最大子集个数
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
//2.状态转移方程:dp[i][j] = max(dp[i][j], dp[i - zeroCount][j - oneCount] + 1);
//3.初始化:所有的值默认为0
for (int i = 0; i < strs.size(); i++)
{
int zeroCount = 0, oneCount = 0;
for (char c : strs[i])
{
if (c == '0') zeroCount++;
else oneCount++;
}
//4.遍历顺序:相比于01背包,我们此处的jk都是背包容量那个维度的值,相当于滚动数组那块的逆序遍历
for (int j = m; j >= zeroCount; j--)
{
for (int k = n; k >= oneCount; k--)
{
dp[j][k] = max(dp[j][k], dp[j - zeroCount][k - oneCount] + 1);
}
}
}
return dp[m][n];
}
十三、完全背包理论基础
1.二维数组表示
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, v;
cin >> n >> v;
vector<int> weight(n, 0), value(n, 0);
for (int i = 0; i < n; i++) cin >> weight[i] >> value[i];
//1.dp[i][j]:前i个物体(可以无限次使用)装入容量为j的背包中表示的最大价值
vector<vector<int>> dp(n, vector<int>(v + 1, 0));
//2.状态转移公式:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
//3.初始化:初始化第0件物品
for (int j = weight[0]; j <= v; j++) dp[0][j] = value[0] * (j / weight[0]);
//4.遍历顺序
for (int i = 1; i < n; i++)
{
for (int j = 0; j <= v; j++)
{
dp[i][j] = dp[i - 1][j];
if (j >= weight[i]) dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
}
}
cout << dp[n - 1][v] << endl;
return 0;
}
2.一维数组表示
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n, v;
cin >> n >> v;
vector<int> weight(n, 0), value(n, 0);
for (int i = 0; i < n; i++) cin >> weight[i] >> value[i];
//1.dp[j]:(可以无限次使用)物品装入容量为j的背包中表示的最大价值
vector<int> dp(v + 1, 0);
//2.状态转移公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
//3.初始化:无需初始化
//4.遍历顺序
for (int i = 0; i < n; i++)
{
for (int j = weight[i]; j <= v; j++)
{
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[v] << endl;
return 0;
}
十四、518. 零钱兑换 II — 组合问题
//完全背包问题,组合数
int change(int amount, vector<int>& coins) {
//1.dp[j]:组成总金额为j的组合数
vector<int> dp(amount + 1, 0);
//2.状态转移方程:dp[j] += dp[j - coins[i]];(看到组合数就要想到这个转移方程)
//3.初始化:组合问题必须需要初始化,否则结果全为0(肥肠重要,每次我都记不住)
dp[0] = 1;
//4.遍历顺序
for (int i = 0; i < coins.size(); i++)
{
for (int j = coins[i]; j <= amount; j++)
{
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
十五、377. 组合总和 Ⅳ — 排列问题
//完全背包问题,排列数
int combinationSum4(vector<int>& nums, int target) {
//1.dp[i]:组成target的排列个数
vector<uint64_t> dp(target + 1, 0);
//2.状态转移方程:dp[i] += dp[i - coins[j]];(看到组合数排列数就要想到这个转移方程)
//3.初始化:排列组合问题必须需要初始化,否则结果全为0(肥肠重要,每次我都记不住)
dp[0] = 1;
//4.遍历顺序:相比于组合问题,排列问题就需要先便利目标数,在遍历nums数组,这样才能有排列的效果
for (int i = 0; i <= target; i++)
{
for (int j = 0; j < nums.size(); j++)
{
if (i >= nums[j]) dp[i] += dp[i - nums[j]];
}
}
return dp[target];
}
十六、57. 爬楼梯(第八期模拟笔试) — 排列问题
#include <iostream>
#include <vector>
using namespace std;
//完全背包问题,排列问题
int main()
{
int n, m;
cin >> n >> m;
//1.dp[i]:爬至第i阶楼梯有dp[i]种不同的方法
vector<int> dp(n + 1, 0);
//2.状态转移方程:dp[i] += dp[i - j];
//3.初始化:
dp[0] = 1;
//4.遍历顺序
for (int i = 0; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (i >= j) dp[i] += dp[i - j];
}
}
cout << dp[n];
return 0;
}
十七、322. 零钱兑换
//完全背包问题,组合数
int coinChange(vector<int>& coins, int amount) {
int n = coins.size();
//1.dp[j]:凑成总金额j所需的最少的硬币个数.
vector<uint64_t> dp(amount + 1, INT_MAX);
//2.状态转移方程:dp[j] = min(dp[j], dp[j - coins[i]] + 1);
//3.初始化:
dp[0] = 0;
//4.遍历顺序
for (int i = 0; i < n; i++)
{
for (int j = coins[i]; j <= amount; j++)
{
dp[j] = min(dp[j], dp[j - coins[i]] + 1);
}
}
if (dp[amount] >= INT_MAX) return -1;
return dp[amount];
}
十八、279. 完全平方数
//完全背包问题,组合问题
int numSquares(int n) {
//1.dp[j]:和为 j 的完全平方数的最少数量.
vector<uint64_t> dp(n + 1, INT_MAX);
//2.状态转移方程:dp[j] = min(dp[j], dp[j - i * i] + 1);
//3.初始化:
dp[0] = 0;
//4.遍历顺序:
for (int i = 1; i * i <= n; i++)
{
for (int j = i * i; j <= n; j++)
{
dp[j] = min(dp[j], dp[j - i * i] + 1);
}
}
return dp[n];
}
十九、139. 单词拆分
//完全背包问题,排列问题
bool wordBreak(string s, vector<string>& wordDict) {
//匹配单词的哈希表
unordered_set<string> uset(wordDict.begin(), wordDict.end());
int n = s.size();
//1.dp[i]:前i个字符是否可以用wordDict的字符串列表作为字典
vector<bool> dp(n + 1, false);
//2.状态转移方程:if (word == 可以被匹配 dp[j] = true) dp[i] = true;
//3.初始化:没有实际意义,但是为了保证结果正确
dp[0] = true;
//4.遍历顺序:先背包后物品,这块的物品是需要我们自己匹配的,所以我们需要从0开始遍历物品
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
{
string word = s.substr(j, i - j);
if (uset.find(word) != uset.end() && dp[j] == true) dp[i] = true;
}
}
//for (int i = 0; i <= n; i++) cout << dp[i] << " ";
return dp[n];
}
二十、56. 携带矿石资源(第八期模拟笔试)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int c, n;
cin >> c >> n;
vector<int> weight(n, 0), value(n, 0), nums(n, 0);
for (int i = 0; i < n; i++) cin >> weight[i];
for (int i = 0; i < n; i++) cin >> value[i];
for (int i = 0; i < n; i++) cin >> nums[i];
//1.dp[i][j]:前i个物品装入容量为j的背包可以存储的最大价值
vector<vector<int>> dp(n, vector<int>(c + 1, 0));
//2.状态转移方程:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - k * weight[i]] + k * value[i]);
//3.初始化
for (int j = 0; j <= c; j++)
{
if (j / weight[0] >= nums[0]) dp[0][j] = value[0] * nums[0];
else dp[0][j] = value[0] * (j / weight[0]);
}
//4.遍历顺序
for (int i = 1; i < n; i++)
{
for (int j = 0; j <= c; j++)
{
dp[i][j] = dp[i - 1][j];
for (int k = 1; k <= nums[i]; k++)
{
if (j >= k * weight[i])
dp[i][j] = max(dp[i][j], dp[i - 1][j - k * weight[i]] + k * value[i]);
}
}
}
cout << dp[n - 1][c];
return 0;
}
二十一、198. 打家劫舍
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
if (n == 1) return nums[0];
//1.dp[i]:盗取第i间房屋的最高金额
vector<int> dp(n, 0);
//2.状态转移方程:dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
//3.初始化
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
//4.遍历顺序
for (int i = 2; i < n; i++)
{
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
二十二、213. 打家劫舍 II
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
//如果连成一个圈的话,那么就需要分成三种情况,1.考虑第一个不考虑最后一个;
//2.考虑最后一个不考虑第一个
//3.第一个和最后一个都不考虑
//最后取得最大值,但是第一种情况和第二种情况中会包含第三种情况,所以只需要遍历1,2就可以了
return max(RobRange(nums, 0, nums.size() - 2), RobRange(nums, 1, nums.size() - 1));
}
int RobRange(vector<int> nums, int begin, int end)
{
int n = nums.size();
if (begin == end) return nums[begin];
//1.dp[i]:盗取第i间房屋的最高金额
vector<int> dp(n, 0);
//2.状态转移方程:dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
//3.初始化
dp[begin] = nums[begin];
dp[begin + 1] = max(nums[begin], nums[begin + 1]);
//4.第一种情况
for (int i = begin + 2; i <= end; i++)
{
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[end];
}
二十三、337. 打家劫舍 III
//只能使用后序遍历
//0表示不偷该节点,1表示偷该节点
vector<int> robTree(TreeNode* cur)
{
if (cur == nullptr) return {0, 0};
vector<int> left = robTree(cur -> left);
vector<int> right = robTree(cur -> right);
//偷该节点
int val1 = cur -> val + left[0] + right[0];
//不偷该节点,考虑是否偷子节点
int val2 = max(left[0], left[1]) + max(right[0], right[1]);
return {val2, val1};
}
int rob(TreeNode* root) {
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
二十四、121. 买卖股票的最佳时机
//动态规划
int maxProfit(vector<int>& prices) {
int n = prices.size();
//1.dp[i][0]:第i天没有持有股票时身上的钱
//dp[i][1]:第i天持有股票时身上的钱
vector<vector<int>> dp(n, vector<int>(2, 0));
//2.状态转移方程:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
//dp[i][1] = max(dp[i - 1][1], -prices[i]); //只能买一个,所以直接是-prices
//3.初始化
dp[0][0] = 0;
dp[0][1] = -prices[0];
//4.遍历顺序:依次遍历
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], -prices[i]);
}
return dp[n - 1][0];
}
二十五、122. 买卖股票的最佳时机 II
int maxProfit(vector<int>& prices) {
int n = prices.size();
//1.dp[i][0]:第i天没有持有股票时身上的钱
//dp[i][1]:第i天持有股票时身上的钱
vector<vector<int>> dp(n, vector<int>(2, 0));
//2.状态转移方程:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
//dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
//3.初始化
dp[0][0] = 0;
dp[0][1] = -prices[0];
//4.遍历顺序:依次遍历
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
}
二十六、123. 买卖股票的最佳时机 III
//相对于之前的买卖股票的两个状态,这次需要四种状态
int maxProfit(vector<int>& prices) {
int n = prices.size();
//1.dp[i][0]:第i天第一次持有股票是身上的钱
//dp[i][1]:第i天第一次卖出股票是身上的钱
//dp[i][2]:第i天第二次持有股票是身上的钱
//dp[i][3]:第i天第二次卖出股票是身上的钱
vector<vector<int>> dp(n, vector<int>(4, 0));
//2.状态转移方程:dp[i][0] = max(dp[i - 1][0], -prices[i]);
//dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
//dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] - prices[i]);
//dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] + prices[i]);
//3.初始化
dp[0][0] = -prices[0]; dp[0][2] = -prices[0];
dp[0][1] = 0; dp[0][3] = 0;
//4.遍历顺序
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], -prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] - prices[i]);
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] + prices[i]);
}
return max(dp[n - 1][1], dp[n - 1][3]);
}
二十七、188. 买卖股票的最佳时机 IV
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
//1.dp数组含义与之前的类似,但是dp[i][0]:没有经过任何操作获得的利润,剩下的依次类推
vector<vector<int>> dp(n, vector<int>(2 * k + 1, 0));
//2.状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i])
//3.状态初始化
for (int j = 1; j <= 2 * k; j += 2) dp[0][j] = -prices[0];
//4.遍历顺序:依次遍历即可
for (int i = 1; i < n; i++)
{
dp[i][0] = dp[i - 1][0];
for (int j = 1; j <= 2 * k; j += 2)
{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);
dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] + prices[i]);
}
}
int result = 0;
for (int j = 0; j <= 2 * k; j += 2) result = max(result, dp[n - 1][j]);
return result;
}
二十八、309. 买卖股票的最佳时机含冷冻期
int maxProfit(vector<int>& prices) {
int n = prices.size();
//1.dp[i][0]:第i天持有股票时身上的钱
//dp[i][1]:第i天身上未持有股票时身上的钱(同时已经过了冷冻期)
//dp[i][2]:第i天当天出售身上的股票后身上的钱
//dp[i][3]:第i天处于冷冻期
vector<vector<int>> dp(n, vector<int>(4, 0));
//2.状态转移方程
//dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
//dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
//dp[i][2] = dp[i - 1][0] + prices[i];
//dp[i][3] = dp[i - 1][2];
//3.初始化:dp[0][1,2,3] = 0
dp[0][0] = -prices[0];
//4.遍历顺序
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][1] - prices[i], dp[i - 1][3] - prices[i]));
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];
}
return max(dp[n - 1][1], max(dp[n - 1][2], dp[n - 1][3]));
}
二十九、714. 买卖股票的最佳时机含手续费
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
//1.dp[i][0]:第i天身上有股票时的最大利润
//dp[i][1]:第i天身上卖出股票时的最大利润
vector<vector<int>> dp(n, vector<int>(2, 0));
//2.状态转移方程:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
//dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
//3.初始化
dp[0][0] = -prices[0];
dp[0][1] = 0;
//4.遍历顺序
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return dp[n - 1][1];
}
三十、718. 最长重复子数组
int findLength(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
int result = 0;
//1.dp[i][j]:nums1的前i个字符与nums2的前j个字符的最长重复子数组大小
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
//2.状态转移方程
//if (nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j -1] + 1;
//3.初始化
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
result = max(result, dp[i][j]);
}
}
return result;
}
三十一、1143. 最长公共子序列
int longestCommonSubsequence(string text1, string text2) {
int n = text1.size();
int m = text2.size();
int result = 0;
//1.dp[i][j]:nums1的前i个字符与nums2的前j个字符的最长重复子数组大小
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
//2.状态转移方程 if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
// else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
//3.初始化 dp[0][0] = 0; 上面已经初始化过了
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[n][m];
}
三十二、718. 最长重复子数组
int findLength(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
int result = 0;
//1.dp[i][j]:nums1的前i个字符与nums2的前j个字符的最长重复子数组大小
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
//2.状态转移方程
//if (nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j -1] + 1;
//3.初始化
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
result = max(result, dp[i][j]);
}
}
return result;
}
三十三、1143. 最长公共子序列
int longestCommonSubsequence(string text1, string text2) {
int n = text1.size();
int m = text2.size();
int result = 0;
//1.dp[i][j]:nums1的前i个字符与nums2的前j个字符的最长重复子数组大小
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
//2.状态转移方程 if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
// else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
//3.初始化 dp[0][0] = 0; 上面已经初始化过了
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[n][m];
}
三十四、1035. 不相交的线
//理解题意的话,其实就是求最长公共子序列
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
//1.dp[i][j]:nums1的前i - 1个字符与nums2的前j - 1个字符的最长公共子序列的长度
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
//2.状态转移方程:if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
//else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
//3.初始化:dp[0][0] = 0;
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[n][m];
}
三十五、53. 最大子数组和
int maxSubArray(vector<int>& nums) {
int n = nums.size();
//1.dp[i]:从0到i的数组中组成的连续子数组最大和
vector<int> dp(n, 0);
//2.状态转移方程:if (dp[i - 1] + nums[i] >= 0) dp[i] = dp[i - 1] + nums[i];
//3.初始化
dp[0] = nums[0];
int result = nums[0];
//4.遍历顺序
for (int i = 1; i < n; i++)
{
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
result = max(result, dp[i]);
}
return result;
}
三十六、392. 判断子序列
bool isSubsequence(string s, string t) {
int n = s.size();
int m = t.size();
//1.dp[i][j]:s的前i - 1个字符中t的前j - 1个字符的子序列的数量
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
//2.状态转移方程:if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
//else dp[i][j] = dp[i][j - 1];
//3.初始化:dp[0][0] = dp[0][1] = dp[1][0] = 0;
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1];
}
}
return dp[n][m] == n;
}
三十七、115. 不同的子序列
int numDistinct(string s, string t) {
int n = s.size();
int m = t.size();
//1.dp[i][j]:t的前j - 1的子序列在s的前i - 1的子序列中出现的个数
vector<vector<uint64_t>> dp(n + 1, vector<uint64_t>(m + 1, 0));
//2.状态转移方程:if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
//else dp[i][j] = dp[i - 1][j];
//3.初始化
for (int i = 1; i <= n; i++) dp[i][0] = 1;
for (int j = 1; j <= m; j++) dp[0][j] = 0;
dp[0][0] = 1;
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
else dp[i][j] = dp[i - 1][j];
}
}
return dp[n][m];
}
三十八、583. 两个字符串的删除操作
int minDistance(string word1, string word2) {
int n = word1.size();
int m = word2.size();
//1.dp[i][j]:word1的前i个字符与word2的前j个字符相同的话所需的步数
vector<vector<int>> dp(n + 1, vector<int>(m + 1, INT_MAX));
//2.状态转移方程:if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
//else dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
//3.初始化
for (int i = 0; i <= n; i++) dp[i][0] = i;
for (int j = 0; j <= m; j++) dp[0][j] = j;
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;
}
}
return dp[n][m];
}
三十九、72. 编辑距离
int minDistance(string word1, string word2) {
int n = word1.size();
int m = word2.size();
//1.dp[i][j]:word1的前i个字符转换成word2的前j个字符所需要的最少操作数
vector<vector<int>> dp(n + 1, vector<int>(m + 1, INT_MAX));
//2.状态转移方程:if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
//else dp[i][j] = min(dp[i - 1][j - 1], max(dp[i - 1][j], dp[i][j - 1])) + 1; 删除和插入可以看成一种操作
//替换相当于将不相等的字符换成相同的那个,也就是 dp[i - 1][j - 1] + 1;
//3.初始化
for (int i = 0; i <= n; i++) dp[i][0] = i;
for (int j = 1; j <= m; j++) dp[0][j] = j;
//4.遍历顺序
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
return dp[n][m];
}
四十、647. 回文子串
int countSubstrings(string s) {
int n = s.size();
int result = 0;
//1.dp[i][j]:字符串s的i-j的字符串是不是回文串
vector<vector<bool>> dp(n, vector<bool>(n, false));
//2.状态转移方程 if (s[i] == s[j]) {if (j - i <= 1) dp[i][j] = true;
//else if (dp[i + 1][j - 1]) dp[i][j] = true }
//3.初始化
//4.遍历顺序:根据状态转移方程可得,dp数组更新依赖于其左下角的值,并且i <= j
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
if (s[i] == s[j])
{
if (j - i <= 1) dp[i][j] = true;
else if (dp[i + 1][j - 1]) dp[i][j] = true;
}
if (dp[i][j]) result++;
}
}
return result;
}
四十一、516. 最长回文子序列
int longestPalindromeSubseq(string s) {
int n = s.size();
//1.dp[i][j]:s的[i,j]范围内的最长的回文子序列的长度
vector<vector<int>> dp(n, vector<int>(n, 0));
//2.状态转移方程:if (s[i] == s[j]) dp[i][j] = dp[i + 1][j - 1] + 2;
//else dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
//3.初始化
for (int i = 0; i < n; i++) dp[i][i] = 1;
//4.遍历顺序
for (int i = n - 1; i >= 0; i--)
{
for (int j = i + 1; j < n; j++)
{
if (s[i] == s[j]) dp[i][j] = dp[i + 1][j - 1] + 2;
else dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
}
}
return dp[0][n - 1];
}
总结
上述是对我学习的代码随想录的动态规划篇的总结,里面有着我对这些代码的想法以及卡哥带给我的思路。