动态规划(Dynamic Programming)

淩亂°似流年 2022-09-12 04:48 352阅读 0赞

文章目录

  • 一、Dynamic Programming定义
  • 二、斐波那契数列
  • 三、跳台阶扩展问题
  • 四、最大连续子数组和
  • 五、背包问题
      • 1.单词拆分 (字符串分割)
  • 六、三角矩阵(Triangle)
  • 七、路径总数I
  • 八、路径总数II
  • 九、最小路径和(Minimum Path Sum)
  • 十、背包问题
  • 十一、回文串分割最小次数问题
  • 十二、编辑距离
  • 十三、不同的子序列

一、Dynamic Programming定义

动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化无的艺术。

在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。

  • 动态规划具备了以下三个特点:
  • 把原来的问题分解成了几个相似的子问题。
  • 所有的子问题都只需要解决一次。
  • 储存子问题的解。

动态规划的本质,是对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系)。

  • 动态规划问题一般从以下四个角度考虑:
  • 状态定义。
  • 状态间的转移方程定义。
  • 状态的初始化。
  • 返回结果。

状态定义的要求:定义的状态一定要形成递推关系。

一句话概括:三特点四要素两本质。

适用场景:最大值/最小值, 可不可行, 是不是,方案个数。

二、斐波那契数列

斐波那契数列
在这里插入图片描述

  1. class Solution {
  2. public:
  3. int Fibonacci(int n)
  4. {
  5. /*if(n==0||n==1) { return n; } else { return Fibonacci(n-1)+Fibonacci(n-2); }*/
  6. if(n==0||n==1)
  7. {
  8. return n;
  9. }
  10. int n1=0;
  11. int n2=1;
  12. int n3=0;
  13. while(n>1)
  14. {
  15. n3=(n1+n2)%1000000007;
  16. n1=n2;
  17. n2=n3;
  18. n--;
  19. }
  20. return n3;
  21. }
  22. };

三、跳台阶扩展问题

类似pow的求解
在这里插入图片描述

  1. class Solution {
  2. public:
  3. int jumpFloorII(int number)
  4. {
  5. if(number==0||number==1)
  6. {
  7. return number;
  8. }
  9. else
  10. {
  11. int ret=1;
  12. while(number>1)
  13. {
  14. ret=ret*2;
  15. number--;
  16. }
  17. return ret;
  18. }
  19. }
  20. };

四、最大连续子数组和

连续子数组最大和
在这里插入图片描述

  1. class Solution {
  2. public:
  3. int FindGreatestSumOfSubArray(vector<int> array)
  4. {
  5. if(array.size()==0)
  6. {
  7. return 0;
  8. }
  9. int sum=array[0];
  10. for(int i=0;i<array.size();i++)
  11. {
  12. array[i]=max(array[i-1]+array[i],array[i]);
  13. sum=max(sum,array[i]);
  14. }
  15. return sum;
  16. }
  17. };

五、背包问题

单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。拆分时可以重复使用字典中的单词,说明就是一个完全背包!
动规五部曲分析如下:

  1. 确定dp数组以及下标的含义:
    dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。
  2. 确定递推公式:
    如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。

所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。

  1. dp数组如何初始化:
    从递归公式中可以看出,dp[i] 的状态依靠 dp[j]是否为true,那么dp[0]就是递归的根基,dp[0]一定要为true,否则递归下去后面都都是false了。

那么dp[0]有没有意义呢?

dp[0]表示如果字符串为空的话,说明出现在字典里。

但题目中说了“给定一个非空字符串 s” 所以测试数据中不会出现i为0的情况,那么dp[0]初始为true完全就是为了推导公式。

下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。

  1. 确定遍历顺序:
    题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。

还要讨论两层for循环的前后循序。

如果求组合数就是外层for循环遍历物品,内层for遍历背包。

如果求排列数就是外层for遍历背包,内层for循环遍历物品。

本题最终要求的是是否都出现过,所以对出现单词集合里的元素是组合还是排列,并不在意!那么本题使用求排列的方式,还是求组合的方式都可以。

即:外层for循环遍历物品,内层for遍历背包 或者 外层for遍历背包,内层for循环遍历物品 都是可以的。

但本题还有特殊性,因为是要求子串,最好是遍历背包放在外循环,将遍历物品放在内循环。

如果要是外层for循环遍历物品,内层for遍历背包,就需要把所有的子串都预先放在一个容器里。(如果不理解的话,可以自己尝试这么写一写就理解了)。

所以最终我选择的遍历顺序为:遍历背包放在外循环,将遍历物品放在内循环。内循环从前到后。

1.单词拆分 (字符串分割)

单词拆分
在这里插入图片描述
在这里插入图片描述

  1. class Solution {
  2. public:
  3. bool wordBreak(string s, vector<string>& wordDict)
  4. {
  5. unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
  6. vector<bool> dp(s.size()+1,false);
  7. dp[0]=true;
  8. for(int i=1;i<=s.size();i++) //遍历背包
  9. {
  10. for(int j=0;j<i;j++) //遍历物品
  11. {
  12. string word=s.substr(j,i-j); //substr(起始位置,截取的个数)
  13. {
  14. if(dp[j]&&wordSet.find(word)!=wordSet.end())
  15. {
  16. dp[i]=true;
  17. }
  18. }
  19. }
  20. }
  21. return dp[s.size()];
  22. }
  23. };
  • 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)。
  • 空间复杂度:O(n)。

六、三角矩阵(Triangle)

在这里插入图片描述
普通解法:
在这里插入图片描述

在这里插入图片描述
动规做法:

  1. class Solution {
  2. public:
  3. int minimumTotal(vector<vector<int> > &triangle)
  4. {
  5. if(triangle.empty())
  6. {
  7. return 0;
  8. }
  9. int row=triangle.size(); //行数
  10. //起始位置(row-1,j)已经确定过了
  11. //返回结果,最上面也就是开始的地方[0][0];
  12. for(int i=row-2;i>=0;i--)
  13. {
  14. for(int j=0;j<=i;j++)
  15. {
  16. triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
  17. }
  18. }
  19. return triangle[0][0];
  20. }
  21. };

七、路径总数I

路径总数
在这里插入图片描述
在这里插入图片描述

  1. class Solution {
  2. public:
  3. /** * * @param m int整型 * @param n int整型 * @return int整型 */
  4. int uniquePaths(int m, int n)
  5. {
  6. // write code here
  7. if(m<1||n<1)
  8. {
  9. return 0;
  10. }
  11. vector<vector<int>> pathNum(m,vector<int>(n,1));
  12. for(int i=1;i<m;i++ )
  13. {
  14. for(int j=1;j<n;j++)
  15. {
  16. pathNum[i][j]=pathNum[i-1][j]+pathNum[i][j-1];
  17. }
  18. }
  19. return pathNum[m-1][n-1];
  20. }
  21. };

八、路径总数II

在这里插入图片描述
路径总数在这里插入图片描述

  1. class Solution {
  2. public:
  3. /** * * @param obstacleGrid int整型vector<vector<>> * @return int整型 */
  4. int uniquePathsWithObstacles(vector<vector<int> >& obstacleGrid)
  5. {
  6. // write code here
  7. if(obstacleGrid.empty())
  8. {
  9. return 0;
  10. }
  11. int row=obstacleGrid.size();
  12. int col=obstacleGrid[0].size();
  13. vector<vector<int>> pathNum(row,vector<int>(col,0));
  14. for(int i=0;i<row;i++)
  15. {
  16. if(obstacleGrid[i][0]==0)
  17. {
  18. pathNum[i][0]=1;
  19. }
  20. else
  21. {
  22. break;
  23. }
  24. }
  25. for(int j=0;j<col;j++)
  26. {
  27. if(obstacleGrid[0][j]==0)
  28. {
  29. pathNum[0][j]=1;
  30. }
  31. else
  32. {
  33. break;
  34. }
  35. }
  36. for(int i=1;i<row;i++)
  37. {
  38. for(int j=1;j<col;j++)
  39. {
  40. if(obstacleGrid[i][j]==0)
  41. {
  42. pathNum[i][j]=pathNum[i-1][j]+pathNum[i][j-1];
  43. }
  44. }
  45. }
  46. return pathNum[row-1][col-1];
  47. }
  48. };

九、最小路径和(Minimum Path Sum)

在这里插入图片描述
最小路径和

  1. class Solution {
  2. public:
  3. /** * * @param grid int整型vector<vector<>> * @return int整型 */
  4. int minPathSum(vector<vector<int> >& grid)
  5. {
  6. // write code here
  7. if(grid.size()==0)
  8. {
  9. return 0;
  10. }
  11. int row=grid.size();
  12. int col=grid[0].size();
  13. for(int i=1;i<row;i++)
  14. {
  15. grid[i][0]=grid[i-1][0]+grid[i][0];
  16. }
  17. for(int j=1;j<col;j++)
  18. {
  19. grid[0][j]=grid[0][j-1]+grid[0][j];
  20. }
  21. for(int i=1;i<row;i++)
  22. {
  23. for(int j=1;j<col;j++)
  24. {
  25. grid[i][j]=min(grid[i-1][j],grid[i][j-1])+grid[i][j];
  26. }
  27. }
  28. return grid[row-1][col-1];
  29. }
  30. };

十、背包问题

在这里插入图片描述
背包问题2
在这里插入图片描述

  1. class Solution {
  2. public:
  3. /** * @param m: An integer m denotes the size of a backpack * @param A: Given n items with size A[i] * @param V: Given n items with value V[i] * @return: The maximum value */
  4. int backPackII(int m, vector<int> &A, vector<int> &V)
  5. {
  6. // write your code here
  7. int n=A.size();
  8. if(n==0||m==0)
  9. {
  10. return 0;
  11. }
  12. vector<int> maxV(m+1,0);
  13. for(int i=1;i<=n;i++)
  14. {
  15. for(int j=m;j>0;j--)
  16. {
  17. if(A[i-1]<=j)
  18. {
  19. maxV[j]=max(maxV[j],maxV[j-A[i-1]]+V[i-1]);
  20. }
  21. }
  22. }
  23. return maxV[m];
  24. }
  25. };

十一、回文串分割最小次数问题

分割回文串
在这里插入图片描述
在这里插入图片描述

  1. class Solution {
  2. public:
  3. bool isPal(string& s,int start,int end)
  4. {
  5. while(start<end)
  6. {
  7. if(s[start]!=s[end])
  8. {
  9. return false;
  10. }
  11. start++;
  12. end--;
  13. }
  14. return true;
  15. }
  16. int minCut(string s)
  17. {
  18. // write code here
  19. vector<int> minC(s.size()+1);
  20. for(int i=1;i<=s.size();i++)
  21. {
  22. minC[i]=i-1;
  23. }
  24. for(int i=2;i<=s.size();i++)
  25. {
  26. //判断整体
  27. if(isPal(s, 0, i-1))
  28. {
  29. minC[i]=0;
  30. continue;
  31. }
  32. for(int j=1;j<i;j++) //从1开始的
  33. {
  34. //j<i &&[j+1,i]
  35. if(isPal(s, j,i-1))
  36. {
  37. minC[i]=min(minC[i],minC[j]+1);
  38. }
  39. }
  40. }
  41. return minC[s.size()];
  42. }
  43. };

在这里插入图片描述

十二、编辑距离

编辑距离
在这里插入图片描述
在这里插入图片描述

  1. class Solution {
  2. public:
  3. int minDistance(string word1, string word2)
  4. {
  5. // write code here
  6. int len1=word1.size();
  7. int len2=word2.size();
  8. vector<vector<int>> minD(len1+1,vector<int>(len2+1,0));
  9. for(int i=1;i<=len2;i++)
  10. {
  11. minD[0][i]=i;
  12. }
  13. for(int i=1;i<=len1;i++)
  14. {
  15. minD[i][0]=i;
  16. }
  17. for(int i=1;i<=len1;i++)
  18. {
  19. for(int j=1;j<=len2;j++)
  20. {
  21. //插入、删除
  22. minD[i][j]=min(minD[i][j-1],minD[i-1][j])+1;
  23. //替换
  24. if(word1[i-1]==word2[j-1])
  25. {
  26. minD[i][j]=min(minD[i][j],minD[i-1][j-1]);
  27. }
  28. else
  29. {
  30. minD[i][j]=min(minD[i][j],minD[i-1][j-1]+1);
  31. }
  32. }
  33. }
  34. return minD[len1][len2];
  35. }
  36. };

十三、不同的子序列

不同的子序列
在这里插入图片描述

  1. class Solution {
  2. public:
  3. int numDistinct(string S, string T)
  4. {
  5. // write code here
  6. int len1=S.size();
  7. int len2=T.size();
  8. vector<vector<int>> numD(len1+1,vector<int>(len2+1,0));
  9. //F(i,0)=1;
  10. for(int i=0;i<=len1;i++)
  11. {
  12. numD[i][0]=1;
  13. }
  14. for(int i=1;i<=len1;i++)
  15. {
  16. for(int j=1;j<=len2;j++)
  17. {
  18. if(S[i-1]==T[j-1])
  19. {
  20. numD[i][j]=numD[i-1][j-1]+numD[i-1][j];
  21. }
  22. else
  23. {
  24. numD[i][j]=numD[i-1][j];
  25. }
  26. }
  27. }
  28. return numD[len1][len2];
  29. }
  30. };

发表评论

表情:
评论列表 (有 0 条评论,352人围观)

还没有评论,来说两句吧...

相关阅读

    相关 动态规划Dynamic Programming

    首先以LeetCode上面的一个问题来开始动态规划的讲解: 题目描述:你是一个专业的强盗,计划抢劫沿街的房屋。每间房都藏有一定的现金,阻止你抢劫他们的唯一的制约因素就是相邻的

    相关 算法-动态规划 Dynamic Programming

    今天在leetcode刷题的时候,遇到动态规划类型的题目,想更加深入的了解下,就从网上搜了搜,发现博主的这篇文章讲解的很详细,因此分享下,希望给大家都带来帮助。 转载自:[h