title: LC101の6. DP
urlname: fth98y
date: ‘2021-06-18 19:50:06’
tags: [DP]
categories: [算法刷题]


简介

动态规划(DP)将问题拆分成多个子问题,保存子问题的解以避免重复计算。
解决 DP 问题的关键在于找到状态转移方程。
可以通过对动态规划进行空间压缩起到节省空间消耗的效果。

基本动态规划:一维

70. 爬楼梯

题目大意:给定 n 节台阶,每次可以走一步或走两步,求一共有多少种方式可以走完这些台阶。
输入:n = 2 输出:2

解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
思路:简单斐波那契数列

  1. int climbStairs(int n) {
  2. if (n <= 2) return n;
  3. vector<int> dp(n + 1, 1);
  4. for (int i = 2; i <= n; ++i) {
  5. dp[i] = dp[i-1] + dp[i-2];
  6. }
  7. return dp[n];
  8. }

198. 打家劫舍

题目大意:假如你是一个劫匪,并且决定抢劫一条街上的房子,每个房子内的钱财数量各不相同。如果
你抢了两栋相邻的房子,则会触发警报机关。求在不触发机关的情况下最多可以抢劫多少钱。
输入:[1,2,3,1] 输出:4

解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
思路:用 dp[i]表示抢劫到第 i 个房子前,可抢劫的最大数量,因此得到状态转移方程 dp[i] = max(dp[i-1],
nums[i-1] + dp[i-2])

  1. class Solution {
  2. public:
  3. int rob(vector<int>& nums) {
  4. int n = nums.size();
  5. vector<int> dp(n + 1, 0);
  6. dp[1] = nums[0];
  7. for(int i = 2; i <= n; i++){
  8. dp[i] = max(dp[i - 1], nums[i - 1] + dp[i - 2]);
  9. }
  10. return dp[n];
  11. }
  12. };

413. 等差数列划分

题目大意:给定一个数组,求这个数组中连续且等差的子数组一共有多少个。
输入:nums = [1,2,3,4] 输出:3

解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。
思路:

  1. class Solution {
  2. public:
  3. int numberOfArithmeticSlices(vector<int>& nums) {
  4. int n = nums.size();
  5. if (n < 3) return 0;
  6. vector<int> dp(n, 0);
  7. for (int i = 2; i < n; ++i) {
  8. if (nums[i] - nums[i-1] == nums[i-1] - nums[i-2]) {
  9. dp[i] = dp[i-1] + 1;
  10. } }
  11. return accumulate(dp.begin(), dp.end(), 0);
  12. }
  13. };

基本动态规划:二维

64. 最小路径和

题目大意:给定一个 m × n 大小的非负整数矩阵,求从左上角开始到右下角结束的、经过的数字的和最
小的路径。每次只能向右或者向下移动。
LC101の6. DP - 图1
输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7

解释:因为路径 1→3→1→1→1 的总和最小。

  1. class Solution {
  2. public:
  3. int minPathSum(vector<vector<int>>& grid) {
  4. int m = grid.size(), n = grid[0].size();
  5. vector<vector<int>> dp(m,vector<int>(n,0));
  6. for(int i = 0; i < m; i++){
  7. for(int j = 0; j < n; j++){
  8. if(i == 0 && j == 0){
  9. dp[i][j] = grid[i][j];
  10. }else if(i == 0){
  11. //只能往下走
  12. dp[i][j] = dp[i][j - 1] + grid[i][j];
  13. }else if(j == 0){
  14. //只能往右走
  15. dp[i][j] = dp[i - 1][j] + grid[i][j];
  16. }else{
  17. //比较下和右
  18. dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
  19. }
  20. }
  21. }
  22. return dp[m - 1][n - 1];
  23. }
  24. };

542. 01 矩阵

题目大意:给定一个由 0 和 1 组成的二维矩阵,求每个位置到最近的 0 的距离。
LC101の6. DP - 图2
输入:mat = [[0,0,0],[0,1,0],[0,0,0]] 输出:[[0,0,0],[0,1,0],[0,0,0]]
**思路:对每个位置进行四向搜索,会达到恐怖的 O(m2n2)。因此,从左上到右下,从右下到左上分别进行一次。

  1. class Solution {
  2. public:
  3. vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
  4. int m = mat.size(), n = mat[0].size();
  5. vector<vector<int>> dp(m, vector<int>(n, INT_MAX - 1));
  6. // 从左上角开始
  7. for (int i = 0; i < m; i++) {
  8. for (int j = 0; j < n; j++) {
  9. if(mat[i][j] == 0){
  10. dp[i][j] = 0;
  11. }else{
  12. if (i > 0) {
  13. dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1);
  14. }
  15. if (j > 0) {
  16. dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1);
  17. }
  18. }
  19. }
  20. }
  21. // 从右下角开始
  22. for (int i = m - 1; i >= 0; i--) {
  23. for (int j = n - 1; j >= 0; j--) {
  24. if (i + 1 < m) {
  25. dp[i][j] = min(dp[i][j], dp[i + 1][j] + 1);
  26. }
  27. if (j + 1 < n) {
  28. dp[i][j] = min(dp[i][j], dp[i][j + 1] + 1);
  29. }
  30. }
  31. }
  32. return dp;
  33. }
  34. };

221. 最大正方形

题目大意:给定一个二维的 0-1 矩阵,求全由 1 构成的最大正方形面积。
LC101の6. DP - 图3
输入:matrix = [[“1”,”0”,”1”,”0”,”0”],[“1”,”0”,”1”,”1”,”1”],[“1”,”1”,”1”,”1”,”1”],[“1”,”0”,”0”,”1”,”0”]]
输出:4
思路:dp(i, j) 表示以 (i, j)为右下角,且只包含 1 的正方形的边长最大值
如果该位置的值是 0,则 dp(i, j) = 0,因为当前位置不可能在由 1 组成的正方形中;
如果该位置的值是 1,则 dp(i, j) 的值由其上方、左方和左上方的三个相邻位置的 dp 值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:
dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1

  1. class Solution {
  2. public:
  3. int maximalSquare(vector<vector<char>>& matrix) {
  4. int n = matrix.size(), m = matrix[0].size();
  5. if(n == 0 || m == 0) return 0;
  6. vector<vector<int>> dp(n,vector<int>(m));
  7. int maxSide = 0;
  8. for(int i = 0; i < n; i++){
  9. for(int j = 0; j < m; j++){
  10. if(matrix[i][j] == '1'){
  11. if(i == 0 || j == 0){
  12. dp[i][j] = 1;
  13. }else{
  14. dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
  15. }
  16. maxSide = max(maxSide, dp[i][j]);
  17. }
  18. }
  19. }
  20. return maxSide * maxSide;
  21. }
  22. };

分割类问题

279. 完全平方数

题目大意:整数 n ,返回 和为 n 的完全平方数的最少数量 。

  1. class Solution {
  2. public:
  3. int numSquares(int n) {
  4. vector<int> dp(n + 1, INT_MAX);
  5. dp[0] = 0;
  6. for(int i = 1; i <= n; i++){
  7. for(int j = 1; j * j <= i; j++){
  8. dp[i] = min(dp[i], dp[i - j*j] + 1);
  9. }
  10. }
  11. return dp[n];
  12. }
  13. };

91. 解码方法

题目大意:编码规则:’A’ -> “1” ‘B’ -> “2” … ‘Z’ -> “26”,
给出已编码的数字串,返回解码前字母组成的情况数。
LC101の6. DP - 图4

  1. class Solution {
  2. public:
  3. int numDecodings(string s) {
  4. if (s[0] == '0') return 0;
  5. int pre = 1, curr = 1;//dp[-1] = dp[0] = 1
  6. for (int i = 1; i < s.size(); i++) {
  7. int tmp = curr;
  8. if (s[i] == '0')
  9. if (s[i - 1] == '1' || s[i - 1] == '2') curr = pre;
  10. else return 0;
  11. else if (s[i - 1] == '1' || (s[i - 1] == '2' && s[i] >= '1' && s[i] <= '6'))
  12. curr = curr + pre;
  13. pre = tmp;
  14. }
  15. return curr;
  16. }
  17. };

子序列问题

300. 最长递增子序列

题目大意:求最长递归子序列
思路:技巧就是用 dp[i] 表示 以 i 结尾的、最长子序列长度

  1. class Solution {
  2. public:
  3. int lengthOfLIS(vector<int>& nums) {
  4. int ans = 1, n = nums.size();
  5. vector<int> dp(n + 1, 1);
  6. for(int i = 1; i < n; i++){
  7. for(int j = 0; j < i; j++){
  8. if(nums[i] > nums[j]){
  9. dp[i] = max(dp[i], dp[j] + 1);
  10. }
  11. }
  12. ans = max(ans, dp[i]);
  13. }
  14. return ans;
  15. }
  16. };

1143. 最长公共子序列

题目大意:求最长公共子序列
思路:技巧就是 dp[i,j]表示子串 str1[0…i-1],str[0…j-1]的最长公共子序列

  1. class Solution {
  2. public:
  3. int longestCommonSubsequence(string text1, string text2) {
  4. int n = text1.size(), m = text2.size();
  5. vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
  6. for(int i = 1; i <= n; i++){
  7. for(int j = 1; j <= m; j++){
  8. if (text1[i - 1] == text2[j - 1]) {
  9. dp[i][j] = dp[i - 1][j - 1] + 1;
  10. } else {
  11. dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
  12. }
  13. }
  14. }
  15. return dp[n][m];
  16. }
  17. };

背包问题

背包问题是一种组合优化的 NP 完全问题:有 N 个物品容量为 W 的背包,每个物品都有自己的体积 w价值 v,求拿哪些物品可以使得背包所装下物品的总价值最大。如果限定每种物品只能选择 0 个或 1 个,则问题称为 0-1 背包问题;如果不限定每种物品的数量,则问题称为无界背包问题或完全背包问题。

普通背包:

  1. int knapsack(vector<int> weights, vector<int> values, int N, int W) {
  2. vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
  3. for (int i = 1; i <= N; ++i) {
  4. int w = weights[i-1], v = values[i-1];
  5. for (int j = 1; j <= W; ++j) {
  6. if (j >= w) {
  7. dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v);
  8. } else {
  9. dp[i][j] = dp[i-1][j];
  10. }
  11. }
  12. }
  13. return dp[N][W];
  14. }

空间优化

  1. int knapsack(vector<int> weights, vector<int> values, int N, int W) {
  2. vector<int> dp(W + 1, 0);
  3. for (int i = 1; i <= N; ++i) {
  4. int w = weights[i-1], v = values[i-1];
  5. for (int j = W; j >= w; --j) {
  6. dp[j] = max(dp[j], dp[j-w] + v);
  7. }
  8. }
  9. return dp[W];
  10. }

完全背包:

  1. int knapsack(vector<int> weights, vector<int> values, int N, int W) {
  2. vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
  3. for (int i = 1; i <= N; ++i) {
  4. int w = weights[i-1], v = values[i-1];
  5. for (int j = 1; j <= W; ++j) {
  6. if (j >= w) {
  7. dp[i][j] = max(dp[i-1][j], dp[i][j-w] + v);
  8. } else {
  9. dp[i][j] = dp[i-1][j];
  10. }
  11. }
  12. }
  13. return dp[N][W];
  14. }

空间优化

  1. int knapsack(vector<int> weights, vector<int> values, int N, int W) {
  2. vector<int> dp(W + 1, 0);
  3. for (int i = 1; i <= N; ++i) {
  4. int w = weights[i-1], v = values[i-1];
  5. for (int j = w; j <= W; ++j) {
  6. dp[j] = max(dp[j], dp[j-w] + v);
  7. }
  8. }
  9. return dp[W];
  10. }

416. Partition Equal Subset Sum

字符串编辑

72. 编辑距离

题目大意:输入两个字符串 s1,s2,输出最少步骤(插入,删除,修改)使 s1==s2
思路:用 dp[i][j]表示只取 s1 前 i 位和 s2 前 j 位,需要的最少步骤。
不改是 dp[i-1][j-1],修改是 dp[i-1][j-1]+1,插入 i 删除 j 是 dp[i][j-1]+1,插入 j 删除 i 是 dp[i-1][j]+1,比对最小值

  1. class Solution {
  2. public:
  3. int minDistance(string word1, string word2) {
  4. int len1 = word1.length();
  5. int len2 = word2.length();
  6. vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
  7. for(int i = 0; i <= len1; i++){
  8. for(int j = 0; j <= len2; j++){
  9. if(i == 0){
  10. dp[i][j] = j;
  11. }else if(j == 0){
  12. dp[i][j] = i;
  13. }
  14. else{
  15. dp[i][j] = min(dp[i-1][j-1] + ((word1[i-1] == word2[j-1])? 0: 1),min(dp[i-1][j] + 1, dp[i][j-1] + 1));
  16. }
  17. }
  18. }
  19. return dp[len1][len2];
  20. }
  21. };

650. 只有两个键的键盘

题目大意:给定字母 A,只可以复制全部或粘贴,求最少几次到指定长度
思路:用 dp[i]表示到 i 位置的最少次数,对于 j 位置,如果 j 可以被 i 整除,就相当于在 dp[j]的基础上进行粘贴,dp[i] = dp[j] + dp[i/j]

  1. class Solution {
  2. public:
  3. int minSteps(int n) {
  4. vector<int> dp(n + 1);
  5. for (int i = 2; i <= n; ++i) {
  6. dp[i] = i;
  7. for (int j = 2; j <= sqrt(n); ++j) {
  8. if (i % j == 0) {
  9. dp[i] = dp[j] + dp[i/j];
  10. break;
  11. }
  12. }
  13. }
  14. return dp[n];
  15. }
  16. };

10. 正则表达式匹配

题目大意:字符串 a 是否满足正则表达式 b(只有字母和 . 和
*思路:

  1. class Solution {
  2. public:
  3. bool isMatch(string s, string p) {
  4. int m = s.size(), n = p.size();
  5. vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
  6. dp[0][0] = true;
  7. for (int i = 1; i < n + 1; ++i) {
  8. if (p[i-1] == '*') {
  9. dp[0][i] = dp[0][i-2];
  10. }
  11. }
  12. for (int i = 1; i < m + 1; ++i) {
  13. for (int j = 1; j < n + 1; ++j) {
  14. if (p[j-1] == '*') {
  15. dp[i][j] = dp[i-1][j-1];
  16. } else if (p[j-1] != '*') {
  17. dp[i][j] = dp[i-1][j-1] && p[j-1] == s[i-1];
  18. } else if (p[j-2] != s[i-1] && p[j-2] != '.') {
  19. dp[i][j] = dp[i][j-2];
  20. } else {
  21. dp[i][j] = dp[i][j-1] || dp[i-1][j] || dp[i][j-2];
  22. }
  23. }
  24. }
  25. return dp[m][n];
  26. }
  27. };

股票交易

这个买票股票系列 LeetCode101 中省略了三道,手动补充完整

额外参考:股票问题系列

121. 买卖股票的最佳时机

题目大意:给定股票每天的价格,只能买卖一次,求最大收益
思路:遍历,维护两个值,最大收益 = max 当前价格 - 左边最小价格,最小价格 = min 左边最小价格

  1. class Solution {
  2. public:
  3. int maxProfit(vector<int>& prices) {
  4. int minprice = 1e9, maxprofit = 0;
  5. for (int price: prices) {
  6. maxprofit = max(maxprofit, price - minprice);
  7. minprice = min(price, minprice);
  8. }
  9. return maxprofit;
  10. }
  11. };

122. 买卖股票的最佳时机 II

题目大意:给定股票每天的价格,可以买卖无限次,但手里最多一支股票,求最大收益
思路:

123. 买卖股票的最佳时机 III

题目大意:给定股票每天的价格,最多可以买卖两次,但手里最多一支股票,求最大收益
思路:

188. 买卖股票的最佳时机 IV

题目大意:给定股票每天价格,最多可以买卖k 次,每次只能有一支股票,求最大收益
思路:

309. 最佳买卖股票时机含冷冻期

题目大意:给定股票每天价格,最多可以买卖k 次,卖出有一天冷冻期不能买,每次只能有一支股票,求最大收益
思路:

练习