专题6--动态规划

1、动态规划基础知识

什么情况下可能是动态规划?满足下面三个条件之一:
1. Maximum/Minimum -
- 最大最小,最长,最短;写程序一般有max/min。
2. Yes/No----是否可行;写程序一般有||。
3. Count(*)--
数方案的个数,比如有多少条路径这种。初始化0个的情况下,初始化为1,联想组合数学里面0! = 1。
则 “极有可能”是使用动态规划求解。
什么情况下可能不是动态规划?

1)如果题目需要求出所有 “具体 ”的方案而非方案 “个数 ”;

2)输入数据是一个 “集合 ”而不是 “序列 ”。(区分就是调整元素顺序看是否对求解有影响)

 动态规划的4点要素:
1) 状态 State
  灵感,创造力,存储小规模问题的结果
  a) 最优解/Maximum/Minimum
  b) Yes/No
  c) Count(*)
2) 方程 Function
  状态之间的联系,怎么通过小的状态,来求得大的状态
3) 初始化 Intialization
  最极限的小状态是什么, 起点
4) 答案 Answer
  最大的那个状态是什么,终点

动态规划有四类:

1. Matrix DP (15%)
2. Sequence (40%)
3. Two Sequences DP (40%)
*4. Others (5%)

动态规划就是  *解决了重复计算 * 的搜索。
动态规划的实现方式:
1.  记忆化搜索;
2.  循环。

循环求解更 *正规 *,存在空间优化的可能,大多数面试官可以接受;(滚动数组)

记忆化搜索空间耗费更大,但思维难度小,编程难度小,时间效率很多情况下更高,少数有 *水平 *的面试官可以接受。

动态规划时间复杂度分析就是看有几个for循环。

3、Matrix DP

state: f[x][y] 表示我从起点走到坐标 x,y……

function: 研究走到 x,y这 个点之前的一步
intialize: 起点
answer: 终点

小技巧:intialize: f[0][0] = A[0][0]
              // f[i][0] = sum(0,0 -> i,0)
              // f[0][i] = sum(0,0 -> 0,i)

初始化的时候需要初始化f[i][0] ,f[0][i] ,这样可以在i-1的时候避免边界检查。

3.1、120. Triangle

https://leetcode.com/problems/triangle/#/description

找到和最小的一条路径。

思路:

1)记忆化搜索memorize+ divide and conquer

  1. class Solution {
  2. public:
  3. int helper(int x,int y,int size,vector<vector<int>>& triangle,vector<vector<int>>& minSum){
  4. if(x >= size){
  5. return ;
  6. }
  7. if(minSum[x][y] != INT_MAX){
  8. return minSum[x][y];
  9. }
  10.  
  11. minSum[x][y] = min(helper(x + ,y, size,triangle,minSum),
  12. helper(x + ,y + ,size,triangle,minSum))
  13. + triangle[x][y];
  14. return minSum[x][y];
  15.  
  16. }
  17. int minimumTotal(vector<vector<int>>& triangle) {
  18. if(triangle.size() == || triangle[].size() == ){
  19. return -;
  20. }
  21. int row = triangle.size();
  22. int col = triangle[row - ].size();
  23. vector<vector<int>> minSum(row,vector<int> (col,INT_MAX));
  24. return helper(,,triangle.size(),triangle,minSum);
  25. }
  26.  
  27. };

memorize + divide and conquer

不管是二叉树还是其他这种树结构的,都是递归到叶子节点的下一个空节点再返回,这点可以节省很多思考过程,很好用。

  1. if(x == size){
  2. return ;
  3. }

所谓记忆化搜索就是指用一个数组存储之前的状态,首先初始化数组全为INT_MAX,计算之前先看看是否计算过,计算过就直接返回。

  1. if(minSum[x][y] != INT_MAX){
  2. return minSum[x][y];
  3. }

2)动态规划:

使用动态规划四步走方法,状态代表 state: f[x][y] = minimum path value from x,y to bottom,initialization:初始化最后一行,function:当前和等于当前值加上下一行的最小值,return:f[0][0]。

  1. class Solution {
  2. public:
  3. //bottom to top
  4. int minimumTotal(vector<vector<int>>& triangle) {
  5. // state: f[x][y] = minimum path value from x,y to bottom
  6. int col = triangle.size();
  7. int row = triangle[col - ].size();
  8. vector<vector<int>> f(col,vector<int> (row,));//f[i][j]代表从下到(i,j)位置的最小和
  9. //initial
  10. for(int i = ;i< row;++i){
  11. f[col - ][i] = triangle[col - ][i];
  12. }
  13. //function,bottom to top
  14. for(int i = col - ;i >= ;--i){
  15. for(int j = ;j < row;++j){
  16. f[i][j] = triangle[i][j] + min(f[i + ][j],f[i + ][j + ]);
  17. }
  18. }
  19. //result
  20. return f[][];
  21. }
  22. };

triangle dynamic programming

3.2  64. Minimum Path Sum

https://leetcode.com/problems/minimum-path-sum/tabs/description

思路:题目意思是从左上走到右下。和上面那题差不多。注意初始化第一行和第一列,然后function的时候从第二列和第二行开始,避免了  i-1 的边界检查。

  1. class Solution {
  2. public:
  3. int minPathSum(vector<vector<int>>& grid) {
  4. if(grid.size() == || grid[].size() == ){
  5. return -;
  6. }
  7. int m = grid.size();
  8. int n = grid[].size();
  9. //state
  10. vector<vector<int>> sum(m,vector<int> (n,));
  11. //intialization
  12. sum[][] = grid[][];
  13. for(int i = ;i < n;++i){
  14. sum[][i] = sum[][i - ] + grid[][i];
  15. }
  16. for(int i = ;i < m;++i){
  17. sum[i][] = sum[i - ][] + grid[i][];
  18. }
  19. //function
  20. for(int i = ;i < m;++i){
  21. for(int j = ;j < n;++j){
  22. sum[i][j] = min(sum[i - ][j],sum[i][j - ]) + grid[i][j];
  23. }
  24. }
  25. //result
  26. return sum[m - ][n - ];
  27. }
  28. };

minimum path sum

3.3 62. Unique Paths

https://leetcode.com/problems/unique-paths/tabs/description

机器人从左上走到右下有多少条路径。

思路:按照动态规划四步走策略,num[i][j]代表从起点到(i,j)这点路径之和;矩阵型动态规划初始化第一行和第一列,都为1;function等于top和left的路径之和;

结果返回num[m - 1][n - 1].

  1. class Solution {
  2. public:
  3. int uniquePaths(int m, int n) {
  4. if(m == || n == ){
  5. return -;
  6. }
  7. //state
  8. vector<vector<int>> num(m,vector<int> (n,));
  9. //initialation
  10. num[][] = ;
  11. for(int i =;i < n;++i){
  12. num[][i] = ;
  13. }
  14. for(int j =;j < m;++j){
  15. num[j][] = ;
  16. }
  17. //function
  18. for(int i = ;i < m;++i){
  19. for(int j = ;j < n;++j){
  20. num[i][j] = num[i - ][j] + num[i][j - ];
  21. }
  22. }
  23. //result
  24. return num[m - ][n - ];
  25. }
  26. };

unique paths

3.4 63. Unique Paths II

https://leetcode.com/problems/unique-paths-ii/tabs/description/

机器人从左上走到右下有多少条路径。有障碍物的情况。

思路:这题和上题不同点在于有障碍物,对num[i][j]进行计算前要判断当前格子的值是否为1,为1的话就num赋值为0,不然就等于top和left的num值之和。

记住小细节:第一行和第一列进行赋值的时候,要注意先判断不是1,就将num赋值为1,不然直接break退出循环,因为vector定义的时候就已经初始化为0;

  1. class Solution {
  2. public:
  3. int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
  4. //exception
  5. if(obstacleGrid.size() == || obstacleGrid[].size() == ){
  6. return -;
  7. }
  8. //state
  9. int m = obstacleGrid.size();
  10. int n= obstacleGrid[].size();
  11. vector<vector<int>> num(m,vector<int> (n,));
  12. //initialation
  13.  
  14. for(int i = ;i < n;++i){
  15. if(obstacleGrid[][i] != ){
  16. num[][i] = ;
  17. }
  18. else{
  19. break;
  20. }
  21. }
  22. for(int j = ;j < m;++j){
  23. if(obstacleGrid[j][] != ){
  24. num[j][] = ;
  25. }
  26. else{
  27. break;
  28. }
  29. }
  30. //function
  31. for(int i = ;i < m;++i){
  32. for(int j = ;j < n;++j){
  33. if(obstacleGrid[i][j] == ){
  34. num[i][j] = ;
  35. }
  36. else{
  37. num[i][j] = num[i][j - ] + num[i - ][j];
  38. }
  39. }
  40. }
  41. //answer
  42. return num[m - ][n - ];
  43. }
  44. };

Unique Paths II

4 序列型动态规划sequence dynamic programming

state: f[i]表示 “ 前 i” 个位置 /数字 /字母 ,(以第 i个 为 ).以第i个元素结尾的最值..
function: f[i] = f[j] … j 是 i之前的一个位置
intialize: f[0]..
answer: f[n-1]..

处理字符串类型的题目并且涉及到前i个字符,我们一般讲数组多开一个f(n + 1),数组大小是n + 1.

4.1 70. Climbing Stairs

https://leetcode.com/problems/climbing-stairs/tabs/description

思路:递归,循环,动态规划

大炮打小鸟:动态规划(这题属于count,计算方案个数,同时里面的数不能随意交换,是序列不是集合所以可以使用动态规划)

state: f[i]表示前 i个位置,跳到第 i个位置的方案
总 数
function: f[i] = f[i-1] + f[i-2]
intialize: f[0] = 1
answer: f[n]

  1. class Solution {
  2. public:
  3. int climbStairs(int n) {
  4. //state
  5. vector<int> f(n + ,);
  6. //intialization
  7. f[] = ;
  8. f[] = ;
  9. //function
  10. for(int i = ;i <= n;++i){
  11. f[i] = f[i - ] + f[i - ];
  12. }
  13. //answer
  14. return f[n];
  15. }
  16. };

climbStairs dynamic programming

循环:

  1. class Solution {
  2. public:
  3. int climbStairs(int n) {
  4. int a = ;
  5. int b = ;
  6. int c = ;
  7. for(int i = ;i < n;++i){
  8. c = a + b;
  9. a = b;
  10. b = c;
  11. }
  12. return c;
  13. }
  14. };

climbStairs iterator

4.2 55. Jump Game

https://leetcode.com/problems/jump-game/tabs/description

矩阵中的数字代表跳跃的步数,是否能够跳到最后一格。

思路:首先判断能否使用动态规划:1)属于YES/NO问题,2)元素是有序的不是集合 => 可以使用序列型动态规划

虽然动态规划超时,但是思路需要知道。

这题需要用到贪心算法,计算出整个序列所能达到的最大长度,只要最大长度大于数组的最后一个下标,就可以是true,

  1. class Solution {
  2. public:
  3. bool canJump(vector<int>& nums) {
  4. if(nums.empty()){
  5. return false;
  6. }
  7. int longest = nums[] + ;
  8. for(int i = ;i < nums.size();++i){
  9. if(i <= longest && i + nums[i] > longest){
  10. longest = i + nums[i];
  11. }
  12. }
  13. return longest >= nums.size() - ;
  14. }
  15. };

55. Jump Game

4.3 45. Jump Game II

https://leetcode.com/problems/jump-game-ii/description/

数组中每个数字代表最大跳跃的步数,到达最后一个元素,最少需要多少步数。

思路:动态规划算法会超时,但是需要掌握。因为是最少跳跃步数,所以初始化为最大值INT_MAX;

state: f[i]代表我跳到这个位置最少需要几步
function: f[i] = MIN(f[j]+1, j < i && j能 够 跳到 i),每次操作就是上次跳的最小步数加1;
initialize: f[0] = 0;
answer: f[n-1]

  1. class Solution {
  2. public:
  3. //最少跳跃步数
  4. int jump(vector<int>& nums) {
  5. if(nums.size() == ){
  6. return ;
  7. }
  8. int n = nums.size();
  9. vector<int> dp(n,INT_MAX);
  10. dp[] = ;//初始化了第一个就不需要计算了
  11. int minStep;
  12. for(int i = ;i < n;++i){
  13. minStep = INT_MAX;
  14. for(int j = ;j < i;++j){
  15. if(dp[j] != INT_MAX && nums[j] + j >= i && dp[j] + < minStep){
  16. minStep = dp[j] + ;
  17.  
  18. }
  19. }
  20. dp[i] = minStep;
  21. }
  22. return dp[n - ];
  23. }
  24. };

动态规划超时版本

//贪心算法我自己的理解版本

#一直对贪心不感冒#。。。。cur表示最远能覆盖到的地方。last表示已经覆盖的地方,ret表示步数,以[2,3,1,1,4]为例子;

1、初始化==>cur = 0;ret = 0;last = 0;

2、i= 0的时候==>cur = 2 + 0 = 2;ret = 0;last = 0;

3、i = 1的时候==>i > last,last需要更新为cur,即last = 2,同时cur = 1 + 3 = 4,因为last更新了一次,所以ret需要加1;

  1. class Solution {
  2. public:
  3. int jump(vector<int>& nums) {
  4. if(nums.size() == ){
  5. return -;
  6. }
  7. int len = nums.size();
  8. vector<int> small(len,INT_MAX);
  9. //initalization
  10. small[] = ;
  11. //function
  12. for(int i = ; i < len;++i){
  13. for(int j = ;j < i;++j){
  14. if(small[j] != INT_MAX && j + nums[j] >= i){
  15. small[i] = min(small[i],small[j] + );
  16. }
  17. }
  18. }
  19. //answer
  20. return small[len - ];
  21.  
  22. }
  23. };

jump games II

贪心法:实验室小纸贴校外版参照

  1. class Solution {
  2. public:
  3. int jump(vector<int>& nums) {
  4. if(nums.size() == ){
  5. return -;
  6. }
  7. int len = nums.size();
  8. int cur = ;
  9. int last = ;
  10. int step = ;
  11. for(int i = ;i < len;++i){
  12. if(i > last){
  13. last = cur;
  14. ++step;
  15. }
  16. cur = max(cur,i + nums[i]);
  17. }
  18. return step;
  19. }
  20. };

4.4 300. Longest Increasing Subsequence

https://leetcode.com/problems/longest-increasing-subsequence/description/

思路:f[i]代表num[i]结尾的最长序列,注意该题初始化的时候要全部初始化为1,不能只初始化f[0],因为[3,2,4]这种情况,f[1]没有初始化就为0,导致计算错误。

  1. class Solution {
  2. public:
  3. int lengthOfLIS(vector<int>& nums) {
  4. if(nums.size() == ){
  5. return ;
  6. }
  7. //state
  8. int n = nums.size();
  9. int maxNum = ;
  10. vector<int> f(n,);//must all are equal 1
  11. //intialization
  12. // f[0] = 1;
  13. //function
  14. for(int i = ;i < n;++i){
  15. for(int j = ;j < i;++j){
  16. if(nums[j] < nums[i]){
  17. f[i] = max(f[i],f[j] + );
  18. maxNum = max(maxNum,f[i]);
  19. }
  20. }
  21. }
  22. //answer
  23. return maxNum;
  24. }
  25. };

longest increasing subsequence

4.5 132. Palindrome Partitioning II

https://leetcode.com/problems/palindrome-partitioning-ii/description/

将一个字符串切割为回文串,求最小的切割次数。

思路:1)使用DFS

2)使用动态规划,传统的序列性动态规划,使用双指针方式判断回文的方法复杂度为n^3,超时了,所以使用两个动态规划,一个为区间型动态规划,序列性动态规划需要将数组多设一个,f(n + 1),初始化需要全部初始化,并且将第0个初始化为-1,

**简化回文判断复杂度的区间型动态规划:先判断两端字符是否相等,然后利用数组判断中间部分是否相等。,使用len长度控制。

/////////////////////////////////////////////////////////////////////////////////////*********//////////////////////////////////////////////////

state: f[i]”前i”个字符组成的子字符串需要最少几次cut(最少能被分割为多少个字符串-1)
function: f[i] = MIN{f[j]+1}, j < i && j+1 ~ i这一段是一个回文串
intialize: f[i] = i - 1 (f[0] = -1)
answer: f[s.length()]

/////////////////////////////////////////////////////////////////////////////////////*********//////////////////////////////////////////////////

  1. class Solution {
  2. public:
  3. vector<vector<bool>> isPalindrome(string s){
  4. vector<vector<bool>> result(s.size(),vector<bool> (s.size(),false));
  5. for(int i = ;i < s.size();++i){
  6. result[i][i] = true;
  7. }
  8.  
  9. for(int start = ;start < s.size() - ;++start){
  10. result[start][start + ] = s[start] == s[start + ];
  11. }
  12.  
  13. for(int len = ; len < s.size();++len){
  14. for(int start = ;start < s.size() - len;++start){
  15. result[start][start + len] =
  16. (result[start + ][start + len - ] && s[start] == s[start + len]);
  17.  
  18. }
  19. }
  20. return result;
  21. }
  22. int minCut(string s) {
  23. if(s.size() == ){
  24. return -;
  25. }
  26. vector<vector<bool>> result = isPalindrome(s);
  27. int n = s.size();
  28. //state
  29. vector<int> f(n + ,);//最少需要多少刀
  30. //intialization
  31. for(int i = ;i <= n;++i){
  32. f[i] = i - ;
  33. }
  34. //function
  35. for(int i = ;i <= n;++i){
  36. for(int j = ;j < i;++j){
  37. if(result[j][i - ]){
  38. f[i] = min(f[j] + ,f[i]);
  39. }
  40. }
  41. }
  42. //answer
  43. return f[n];
  44. }
  45. };

palindrome sequence

二刷版本:注意主程序需要考虑i == j。len从1开始,也可以考虑dp[n] ,不一定要dp[n  + 1];

  1. class Solution {
  2. public:
  3.  
  4. void judge(vector<vector<bool>> &res,string s){
  5. for(int i = ;i < res.size();++i){
  6. res[i][i] = true;
  7. }
  8. for(int i = ;i < res.size();++i){
  9. res[i][i + ] = (s[i] == s[i + ]);
  10. }
  11. for(int len = ;len < res.size();++len){
  12. for(int i = ;i < res.size() - len - ;++i){
  13. res[i][i + len + ]= res[i + ][i + len] && (s[i] == s[i + len + ]);
  14. }
  15. }
  16. }
  17.  
  18. int minCut(string s) {
  19. if(s.empty()){
  20. return -;
  21. }
  22. int n = s.size();
  23. vector<vector<bool>> res(n,vector<bool> (n,false));
  24. vector<int> dp(n,);
  25.  
  26. judge(res,s);
  27. for(int i = ;i < n;++i){
  28. dp[i] = i;
  29. }
  30. for(int i = ;i < n;++i){
  31. for(int j = ;j <= i;++j){//这里一定要i==j结束
  32. if(res[j][i]){
  33. if(j == ){
  34. dp[i] = ;
  35. }
  36. else{
  37. dp[i] = min(dp[i],dp[j - ] + );
  38. }
  39.  
  40. }
  41. }
  42. }
  43. return dp[n - ];
  44. }
  45. };

二刷

4.6  139. Word Break

https://leetcode.com/problems/word-break/description/

这道拆分词句问题是看给定的词句能分被拆分成字典里面的内容

思路:就是按照动态规划四步走战略,注意判断前提:首先是yes/no问题,然后是序列不是集合,所以是序列型动态规划。

1)预处理:使用unordered_set,将字典存储,这样以后查找才快;

2)state:vector<bool> f(n + 1,false);//前i个字符是否能分割为dictionary里面的单词;

3)intialization: f[0] = true;空字符肯定是存在的。

4)//function:前j个是字典中的并且j和i中间的单词也是字典中的,那么前i个就是TRUE,

  1. if(f[j] == true && setWordDict.find(subString) != setWordDict.end()){
  2. f[i] = true;
  3. }

5)answer:f[n]。

  1. class Solution {
  2. public:
  3. set<string> WordDict(vector<string>& wordDict){
  4. set<string> result;
  5. for(string str : wordDict){
  6. result.insert(str);
  7. }
  8. return result;
  9. }
  10. bool wordBreak(string s, vector<string>& wordDict) {
  11. if(s.size() == ){
  12. return true;
  13. }
  14. if(wordDict.size() == ){
  15. return false;
  16. }
  17. //preparation
  18. set<string> setWordDict = WordDict(wordDict);
  19. //state
  20. int n = s.size();
  21. vector<bool> f(n + ,false);//前i个字符是否能分割为dictionary里面的单词
  22. //intialization
  23. f[] = true;
  24. //function
  25. for(int i = ;i <= n;++i){
  26. for(int j = ;j < i;++j){
  27. string subString = s.substr(j,i - j);//(i - 1) -j + 1
  28. if(f[j] == true && setWordDict.find(subString) != setWordDict.end()){
  29. f[i] = true;
  30. }
  31. }
  32. }
  33. //answer
  34. return f[n];
  35. }
  36. };

Word break

二刷错误点

1)goals,,字典是go,goal,goals

所以不能一找到第一个go就break。

2)

  1. int ix = ;
  2. for(ix = ;ix < n;++ix){
  3. if(wordSet.find(s.substr(,ix + )) != wordSet.end()){
  4. dp[ix] = true;
  5. //break;
  6. }
  7. }
  8. // if(ix == n){
  9. // return dp[ix - 1];
  10. // }开始这里没注释,直接每次退出循环ix都等于n,总是出错,因为是原来break掉,才有这句
  1. class Solution {
  2. public:
  3. bool wordBreak(string s, vector<string>& wordDict) {
  4. if(s.size() == ){
  5. return false;
  6. }
  7. if(wordDict.size() == ){
  8. return ;
  9. }
  10. int n = s.size();
  11. vector<bool> dp(n,false);
  12. //hashset
  13. unordered_set<string> wordSet;
  14. for(int i = ;i < wordDict.size();++i){
  15. wordSet.insert(wordDict[i]);
  16. }
  17. //find first true
  18. int ix = ;
  19. for(ix = ;ix < n;++ix){
  20. if(wordSet.find(s.substr(,ix + )) != wordSet.end()){
  21. dp[ix] = true;
  22. //break;
  23. }
  24. }
  25. // if(ix == n){
  26. // return dp[ix - 1];
  27. // }开始这里没注释,直接每次退出循环ix都等于n,总是出错,因为是原来break掉,才有这句
  28. //funciton
  29. for(int i = ;i < n;++i){
  30. for(int j = ;j <= i;++j){
  31. if((dp[j - ] == true) && (wordSet.find(s.substr(j,i - j + )) != wordSet.end())){
  32. dp[i] = true;
  33. }
  34. }
  35. }
  36.  
  37. return dp[n - ];
  38. }
  39. };

二刷这题不难就是传统序列动态规划

word break II以及总结

那道题只让我们判断给定的字符串能否被拆分成字典中的词,而这道题加大了难度,让我们求出所有可以拆分成的情况,

5、双序列型动态规划。

一定要注意:不管是双序列还是单序列动态规划,都必须注意求前n个这种情况。比如:seq和f[i],f数组中f[i],对应到seq中的计算必须是seq[i - 1]。

5.1 Longest Common Subsequence

最长公共子序列

http://www.lintcode.com/en/problem/longest-common-subsequence/

思路:1)f[i][j]代表a前i个字符和b前j个字符的相等的长度。

这题最容易错的地方就是:seq和f[i],f数组中f[i],对应到seq中的计算必须是seq[i - 1],毕竟在状态定义中,f数组指的是前i个字符。

2)intialization:初始化为0;

3)function:就是递推方程;

4)answer:f[n][m]。

  1. class Solution {
  2. public:
  3. /**
  4. * @param A, B: Two strings.
  5. * @return: The length of longest common subsequence of A and B.
  6. */
  7. int longestCommonSubsequence(string A, string B) {
  8. // write your code here
  9. if(A.size() == || B.size() == ){
  10. return ;
  11. }
  12. int n = A.size();
  13. int m = B.size();
  14. //state
  15. vector<vector<int>> f(n + ,vector<int> (m + ,));
  16. //intialization
  17.  
  18. //function
  19. for(int i = ;i <= n;++i){
  20. for(int j = ;j <= m;++j){
  21. if(A[i - ] == B[j - ]){
  22. f[i][j ] = f[i - ][j - ] + ;
  23. }
  24. else{
  25. f[i][j] = max(f[i][j - ],f[i - ][j]);
  26. }
  27. }
  28. }
  29. //answer
  30. return f[n][m];
  31. }
  32. };

longest common substring

5.2 72. Edit Distance

https://leetcode.com/problems/edit-distance/description/

将字符串1变为字符串2,最小的编辑距离。

这里注意一个错误信息:min函数里面比较的只能是两个数,如果比较三个数就会出错“required from here 

思路:1)state:f[i][j]a中的前i 个字符经过多少次编辑变为b中的前j个字符。

2)function:f[i][j] = MIN(  f[i-1][j]+1插入,     f[i][j-1]+1删除,       f[i-1][j-1]不变  ) // a[i] == b[j]
                                       = MIN(  f[i-1][j]+1插入,   f[i][j-1]+1删除,        f[i-1][j-1]+1替换  ) // a[i] != b[j]

(帮助记忆:假设i等于j,i-1就是需要增加一个才能等于j,j - 1就需要i删除一个才能使得两者相等)

3)intialize:f[i][0] = i, f[0][j] = j ;

  4)answer: f[a.length()][b.length()]

  1. class Solution {
  2. public:
  3. int minDistance(string word1, string word2) {
  4. int n = word1.size(),m = word2.size();
  5. if(n == && m == ){
  6. return ;
  7. }
  8. //state
  9. vector<vector<int>> f(n + ,vector<int> (m + ,));
  10. //intialization
  11. for(int i = ;i <= n;++i){
  12. f[i][] = i;
  13. }
  14. for(int j = ;j <= m;++j){
  15. f[][j] = j;
  16. }
  17. //function
  18. for(int i = ;i <= n;++i){
  19. for(int j = ;j <= m;++j){
  20. if(word1[i - ] == word2[j - ]){
  21. f[i][j] = min(f[i - ][j - ],min(f[i][j - ] + ,f[i - ][j] + ));
  22. }
  23. else{
  24.  
  25. f[i][j] = min(f[i - ][j - ] + ,min(f[i][j - ] + ,f[i - ][j] + ));
  26. }
  27. }
  28. }
  29. //answer
  30. return f[n][m];
  31. }
  32. };

edit distance

5.3  115. Distinct Subsequences

https://leetcode.com/problems/distinct-subsequences/description/

思路:题目意思是source字符串中找出和target字符串相等的子串有多少个。记住那两个for循环,一个是i <=n,j<= m,只要是双序列动态规划都是这样。

初始化的时候,要注意初始化第一个f[0[0],初始化第一个列和第一行会重复第1个,所以要注意这里。初始化技巧先全部初始化为0,后面就看哪些情况不是0的就特殊处理就好了。

  1. //intialize
  2. for(int i = ;i <= n;++i){
  3. f[i][] = ;
  4. cout << f[i][];
  5. }
  6. for(int j = ;j <= m;++j){//初始化第一个f[0][0]出现冲突,导致出错
  7. f[][j] = ;
  8. }

state: f[i][j] 表示 S的前i个字符中选取T的前j个字符,有多少种方案
function:第j个字符是不能去掉的,都是分为相等和不相等这两种情况进行处理,

                如果i和j相等,就看前i-1个字符和前j个字符的情况以及前i-1和j-1个字符判断,

               不相等的话就看前i-1个字符和前j个字符的情况。
  f[i][j] = f[i - 1][j] + f[i - 1][j - 1] (S[i-1] == T[j-1])
    = f[i - 1][j] (S[i-1] != T[j-1])
initialize: f[i][0] = 1, f[0][j] = 0 (j > 0)
answer: f[n][m] (n = sizeof(S), m = sizeof(T))

  1. class Solution {
  2. public:
  3. int numDistinct(string s, string t) {
  4. int n = s.size();
  5. int m = t.size();
  6. if(n == && m == ){
  7. return ;
  8. }
  9. //state
  10. vector<vector<int>> f(n + ,vector<int> (m + ,));
  11. //intialize
  12. for(int i = ;i <= n;++i){
  13. f[i][] = ;
  14. cout << f[i][];
  15. }
  16. for(int j = ;j <= m;++j){//初始化第一个f[0][0]出现冲突,导致出错
  17. f[][j] = ;
  18. }
  19. //function
  20.  
  21. for(int i = ;i <= n;++i){
  22. for(int j = ;j <= m;++j){
  23. if(s[i - ] == t[j - ]){
  24. f[i][j] = f[i - ][j] + f[i - ][j - ];
  25.  
  26. }
  27. else{
  28. f[i][j] = f[i - ][j];
  29. }
  30. }
  31. }
  32. //answer
  33. return f[n][m];
  34. }
  35. };

distinct subsequence

5.4  97. Interleaving String

s3是否是由s1和s2交错形成的字符串。

思路:先填满一个dp数组,然后想第一行第一列,然后找规律得递推关系。参考交织相错的字符串。只要是遇到字符串的子序列或是匹配问题直接就上动态规划Dynamic Programming,其他的都不要考虑。

技巧:初始化的时候先单独初始化第一个,然后再循环初始化第一行和第一列。

state: f[i][j]表示s1的前i个字符和s2的前j个字符能否交替组成s3的前i+j个字符

前i个和前j个组成前i + j个,对应的下标是i + j - 1 
function: f[i][j] = (f[i-1][j] && (s1[i-1]==s3[i+j-1]) ||  f[i][j-1] && (s2[j-1]==s3[i+j-1])----注意f数组是前i个,对应s数组下标是i-1.
initialize: f[i][0] = s1[0..i-1] = s3[0..i-1]
      f[0][j] = s2[0..j-1] = s3[0..j-1]
answer: f[n][m] n = sizeof(s1), m = sizeof(s2)

  1. class Solution {
  2. public:
  3. bool isInterleave(string s1, string s2, string s3) {
  4. if(s1.size() + s2.size() != s3.size()){
  5. return false;
  6. }
  7. int n = s1.size(),m = s2.size();
  8. //state
  9. vector<vector<bool>> dp(n + ,vector<bool> (m + ,false));
  10. //intialize
  11. dp[][] = true;
  12. for(int i = ;i <= n;++i){
  13. if(s1[i - ] == s3[i - ] && dp[i - ][] == true){
  14. dp[i][] = true;
  15. }
  16. }
  17. for(int j = ;j <= m;++j){
  18. if(s2[j - ] == s3[j - ] && dp[][j - ] == true){
  19. dp[][j] = true;
  20. }
  21. }
  22. //function
  23. for(int i = ;i <= n;++i){
  24. for(int j = ;j <= m;++j){
  25. if(s1[i - ] == s3[i - + j] && dp[i - ][j] || s2[j - ] == s3[i - + j] && dp[i][j - ]){
  26. dp[i][j] = true;
  27. }
  28. }
  29. }
  30. return dp[n][m];
  31. }
  32. };

interleaving string交错字符串

6专题总结-动态规划dynamic programming的更多相关文章

  1. 动态规划Dynamic Programming

    动态规划Dynamic Programming code教你做人:DP其实不算是一种算法,而是一种思想/思路,分阶段决策的思路 理解动态规划: 递归与动态规划的联系与区别 -> 记忆化搜索 -& ...

  2. 动态规划(Dynamic Programming)算法与LC实例的理解

    动态规划(Dynamic Programming)算法与LC实例的理解 希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路.也欢迎大家来给我的Github的Le ...

  3. 动态规划 Dynamic Programming

    March 26, 2013 作者:Hawstein 出处:http://hawstein.com/posts/dp-novice-to-advanced.html 声明:本文采用以下协议进行授权: ...

  4. [算法]动态规划(Dynamic programming)

    转载请注明原创:http://www.cnblogs.com/StartoverX/p/4603173.html Dynamic Programming的Programming指的不是程序而是一种表格 ...

  5. 最优化问题 Optimization Problems & 动态规划 Dynamic Programming

    2018-01-12 22:50:06 一.优化问题 优化问题用数学的角度来分析就是去求一个函数或者说方程的极大值或者极小值,通常这种优化问题是有约束条件的,所以也被称为约束优化问题. 约束优化问题( ...

  6. 动态规划系列(零)—— 动态规划(Dynamic Programming)总结

    动态规划三要素:重叠⼦问题.最优⼦结构.状态转移⽅程. 动态规划的三个需要明确的点就是「状态」「选择」和「base case」,对应着回溯算法中走过的「路径」,当前的「选择列表」和「结束条件」. 某种 ...

  7. 动态规划 Dynamic Programming 学习笔记

    文章以 CC-BY-SA 方式共享,此说明高于本站内其他说明. 本文尚未完工,但内容足够丰富,故提前发布. 内容包含大量 \(\LaTeX\) 公式,渲染可能需要一些时间,请耐心等待渲染(约 5s). ...

  8. Python算法之动态规划(Dynamic Programming)解析:二维矩阵中的醉汉(魔改版leetcode出界的路径数)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_168 现在很多互联网企业学聪明了,知道应聘者有目的性的刷Leetcode原题,用来应付算法题面试,所以开始对这些题进行" ...

  9. 后台开发 3个题目 array_chunk, 100块钱找零钱(动态规划 dynamic programming), 双向循环链表 llist 删除节点

    1. array_chunk 实现 http://php.net/manual/en/function.array-chunk.php <?php function my_array_chunk ...

随机推荐

  1. idea配置jdk,maven,tomcat

    1.idea配置jdk  2.idea配置maven  3.idea配置tomcat

  2. 走过的K8S坑

    基本的docker命令: docker 镜像 打包成文件 sudo docker save -o 打包后的文件名 {镜像ID}或者{镜像标签} docker 改名: docker tag ff2816 ...

  3. 19 JavaScript数组 &数组增删&最值&排序&迭代

    关联数组(散列) 关联数组又叫做散列,即使用命名索引. JavaScript数组只支持数字索引. JavaScript对象使用命名索引,而数组使用数字索引,JavaScript数组是特殊类型的对象. ...

  4. netty代理http&https请求

    (1)关键代码 package test; import java.security.cert.CertificateException; import javax.net.ssl.SSLExcept ...

  5. (转)__attribute__之section 分析详解

    原文地址:__attribute__之section详解 前言 第一次接触 "section" 是在公司的一个STM32的项目代码中,前工程师将所有的初始化函数都使用的" ...

  6. 结对编程任意Android App Demo

    一.产品说明 1.编写目的:用于获取百度图标. 2.情景设计:本产品用于展示图标.随着21世纪各类元素的普及,大部分的人群想下载各类网站的图标,也为了方便用户更便捷的下载而开发的. 3.Demo主要实 ...

  7. MySQL自动备份实战--xtrabackup备份

    MySQL数据备份企业实战.制作shell脚本 功能1:使用xtrabackup以每周为一个备份周期做备份(数据库+二进制日志,备份至本地/data/backup).提示: 周一某个时间点做一次完全备 ...

  8. 5G新时代开启,你会选择哪家运营商?

    牌照正式发放后,5G新时代正式来临.而5G时代显然开了个好头,B站UP主"老师好我叫何同学"发布的<有多快?5G在日常使用中的真实体验>视频,引爆全网.除了仅在B站就获 ...

  9. BinaryTree(二叉树)

    我认为二叉树的递归实现体现了递归思想的一些重要性质,如果对递归的理解不够的话,想利用递归来实现是很费劲的(实际上我现在都还有些懵...),虽然会用,但一些地方不能弄清楚原因. 经过几天的学习,看了许多 ...

  10. Python 之并发编程之线程下

    七.线程局部变量 多线程之间使用threading.local 对象用来存储数据,而其他线程不可见 实现多线程之间的数据隔离 本质上就是不同的线程使用这个对象时,为其创建一个只属于当前线程的字典 拿空 ...