力扣动态规划中等题困难题+字符串DP专题20210807

力扣动态规划中等题困难题+字符串DP专题20210807

总结:

二叉树的DP,采用哈希表map进行存储。自底向上类似后序遍历。

搜索有DFS和BFS,可以理解为,DFS搜索是递归的子集。

一维数组存储可以替换为两个变量或者滚动数组(如果可以的话)。

vector的二维数组申请以及初始化:

1  vector<vector<int>> dp(n+1,vector<int>(3,0));

vector二维数组的行列大小定义:

1     int maximalSquare(vector<vector<char>>& matrix) {
2         int m=matrix.size();
3         int m=matrix[0].size();
4 }

总结:回溯,就是试探性穷举,不成功就退回到上一个状态,继续尝试下一个分支。

力扣 的 139题 一个字符串是否可以在字典集合里完全找到

方法一:采用回溯

代码如下:

 1 class Solution {
 2 private:
 3     //回溯思想,时间复杂度o(2^n),空间复杂度O(n)。
 4     bool backs(string& s, unordered_set<string>& wordSet,vector<int>& memo,int startindex)
 5     {
 6         if(startindex==s.size())
 7         {
 8             return true;
 9         }
10         if(memo[startindex]!=-1)
11         {
12             return memo[startindex];
13         }
14         for(int i=startindex;i<s.size();i++)
15         {
16             string word = s.substr(startindex,i-startindex+1);
17 
18             if(wordSet.find(word)!=wordSet.end()&& backs(s,wordSet,memo,i+1))
19             {
20                 memo[i]=1;
21                 return true;
22             }
23             
24         }
25         memo[startindex]=0;
26         return false;
27     }
28 
29 public:
30     bool wordBreak(string s, vector<string>& wordDict) {
31         unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
32         vector<int> memo(s.size(),-1);//设计一个备忘录,存储重复子问题,比如ab和ab,在第二次扫描到ab的时候,可以直接调用值,而不需要重新计算。
33         return backs(s,wordSet,memo,0);
34     }
35 };

参考链接:https://leetcode-cn.com/problems/word-break/solution/dai-ma-sui-xiang-lu-139-dan-ci-chai-fen-50a1a/

方法二:动态规划

类似01背包问题,

01背包问题模板

代码如下:

 1 class Solution {
 2 
 3 public:
 4     //采用动态规划的思想
 5     bool wordBreak(string s, vector<string>& wordDict) {
 6         unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
 7         vector<bool> dp(s.size()+1,false); // dp[i]表示长度为i的时候,可拆分成1个或者多个在wordSet(字典集合) 里面的单词
 8         dp[0]=true;
 9         for(int i=1;i<=s.size();i++)//这是遍历字符串,相当于物品
10         {
11             for(int j=0;j<i;j++)//这个是单词,相当于背包
12             {
13                 string word = s.substr(j,i-j);
14                 if((wordSet.find(word) != wordSet.end()) && dp[j])
15                 {
16                     dp[i]=true;
17                 }
18             }
19         }
20         return dp[s.size()];
21     }
22 };

力扣 279题 完全平方数

采用动态规划的思想

代码如下:

 1 class Solution {
 2 public:
 3     int numSquares(int n) {
 4         //动态规划的思想
 5         vector<int> dp(n+1);
 6         dp[0]=0;//dp[0]需要初始化为0
 7 
 8         for(int i=1;i<=n;i++)
 9         {
10             int m = INT_MAX;
11             for(int j=1;j*j<=i;j++)
12             {
13                 m = min(m,dp[i-j*j]); //m表示已有一个平方数的基础上,需要的最少数量
14                 dp[i]=1+m;   //       
15             }
16         }
17         return dp[n];
18     }
19 };

力扣583题 两个字符串的删除

两个字符串的最长公共子串模板:

定义:

dp[i][j]表示以i,j结尾的最长公共子串。

转移公式:

dp[i][j]= (dp[i]==dp[j] ? (dp[i-1][j-1]+1) :0)

C++代码模板:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cmath>
 4 #include<cstdio>
 5 using namespace std;
 6  
 7 char s1[1000],s2[1000];
 8 int dp[1000][1000];
 9 int main()
10 {
11     while (~scanf("%s %s",s1+1,s2+1))
12     {
13         int len1 = strlen(s1+1);
14         int len2 = strlen(s2+1);
15         int ans = 0;
16         for (int i = 1;i <= len1; i++)
17           for (int j = 1;j <= len2; j++)
18           {
19               if (s1[i] == s2[j])
20               {
21                   dp[i][j] = dp[i-1][j-1]+1;
22                   ans = max(ans,dp[i][j]);
23               }
24  
25           }
26         printf("%d
",ans);
27         memset(dp,0,sizeof(dp));
28         memset(s1,0,sizeof(s1));
29         memset(s2,0,sizeof(s2));
30     }
31 }

Java代码模板:

 1 public static int lcs(String str1, String str2) {
 2  int len1 = str1.length();
 3  int len2 = str2.length();
 4  int result = 0; //记录最长公共子串长度
 5  int c[][] = new int[len1+1][len2+1];
 6  for (int i = 0; i <= len1; i++) {
 7  for( int j = 0; j <= len2; j++) {
 8   if(i == 0 || j == 0) {
 9   c[i][j] = 0;
10   } else if (str1.charAt(i-1) == str2.charAt(j-1)) {
11   c[i][j] = c[i-1][j-1] + 1;
12   result = max(c[i][j], result);
13   } else {
14   c[i][j] = 0;
15   }
16  }
17  }
18  return result;
19 }

参考链接:https://www.iamivan.net/a/MNv05oM.html

滚动优化:主要是考虑到两个状态可以滚动

C++代码如下:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cmath>
 4 #include<cstdio>
 5 using namespace std;
 6  
 7 char s1[1000],s2[1000];
 8 int dp[2][1000];
 9 int main()
10 {
11     while (~scanf("%s %s",s1+1,s2+1))
12     {
13         int len1 = strlen(s1+1);
14         int len2 = strlen(s2+1);
15         int ans = 0, cur = 0;
16         for (int i = 1;i <= len1; i++)
17         {
18             cur ^= 1;
19             for (int j = 1;j <= len2; j++)
20             {
21                 if (s1[i] == s2[j])
22                 {
23                     dp[cur][j] = dp[cur^1][j-1]+1;
24                     ans = max(ans,dp[cur][j]);
25                 }
26                 else dp[cur][j] = 0;
27  
28             }
29         }
30  
31         printf("%d
",ans);
32         memset(dp,0,sizeof(dp));
33         memset(s1,0,sizeof(s1));
34         memset(s2,0,sizeof(s2));
35     }
36 }

参考链接:https://blog.csdn.net/sugarbliss/article/details/80731688

题目代码:

 1 class Solution {
 2 public:
 3     int minDistance(string word1, string word2) {
 4         //动态规划的思想
 5         int a = word1.size();
 6         int b = word2.size();
 7         vector<vector<int>> dp(a+1,vector<int>(b+1,0));
 8         int ans = 0;
 9         int res=0;
10         for(int i=1;i<=a;i++)
11         {
12             for(int j=1;j<=b;j++)
13             {
14 
15                 if(word1[i-1]==word2[j-1])
16                 {
17                     dp[i][j]= dp[i-1][j-1]+1;
18                     ans = max(ans,dp[i][j]);
19                 }
20                 else
21                 {
22                     dp[i][j] = max(dp[i-1][j],dp[i][j-1]);//当i,j不相等时,dp[i][j]等于前一个状态的值,max(dp[i-1][j],dp[i][j-1])
23                 }
24             }
25         }
26         res = (a+b-(2*dp[a][b]));
27         //cout<<res<<endl;
28         return res;
29     }
30 };

 参考链接:https://leetcode-cn.com/problems/delete-operation-for-two-strings/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-dpzi-8cai1/

力扣 678题 有效的括号字符串

方法一:采用两个栈

代码如下:

 1 class Solution {
 2 public:
 3     bool checkValidString(string s) {
 4     //设两个栈,一个存左括号,一个存星号,遇右括号,先匹配左括号组,再匹配星号组,都没有返回false
 5     //匹配完剩余星号和左括号匹配,注意左括号要在星号左侧,左括号不能有剩余
 6     stack<int> left,star;
 7 
 8     for(int i=0;i<s.size();i++)//把( 和 * 放进两个两个栈里面,遇到 ),就先判断(的栈有的话,就抛出一个;如果(的栈没有,就抛出*的栈里面的一个;都为空,就返回false。
 9     {
10         if(s[i]=='(')
11         {
12             left.push(i);
13         }
14         else if(s[i]=='*')
15         {
16             star.push(i);
17         }
18         else
19         {
20             if(left.empty() && star.empty())
21             {
22                 return false;
23             }
24             if(!left.empty())
25             {
26                 left.pop();
27             }
28             else
29             star.pop();
30         }
31     }
32     while(!left.empty() && !star.empty())//判断左括号和* 可能还没完全消除的情况,如果左括号在*的右边,就是返回false,因为*变成三种方式的任何一种,都不是有效字符串。
33     {
34         if(left.top()>star.top())//这是*(的情况,不是有效字符串
35         {
36             return false;
37         }
38         left.pop();
39         star.pop();
40     }
41     return left.empty();
42     }
43 };

方法二:两个变量的方法

代码如下:

 1 class Solution {
 2 public:
 3     bool checkValidString(string s) {
 4         //两个变量的方法
 5         //对未匹配的左括号设置一个变化的范围,用两个变量分别表示范围的最小值和最大值。一般的括号匹配都是左括号设置一个栈,有右括号,则左括号出栈。可以不用栈,用一个变量表示未匹配的左括号即可。 本题加了*,则需要两个变量表示未匹配的左括号的数量
 6         int min=0;
 7         int max=0;
 8         int n=s.size();
 9         for(int i=0;i<n;i++)
10         {
11             if(s[i]=='(')//左括号的时候,最小值加一,最大值也是加一
12             {
13                 min++;
14                 high++;
15             }
16             else if(s[i]==')')//右括号的时候,最小值是0和减一的最大值,最大值是肯定减一的
17             {
18                 min = max(0,min-1);
19                 high--;
20             }
21             else{          //当出现*的时候,最小值*看成右括号,是0和减一的最大值,最大值是*看成左括号,最大值加一
22                 min = max(0,min-1);
23                 high++;
24             }
25 
26         }
27         return min<=0; //当未匹配的左括号的数量小于等于0的时候才是返回true,否则返回false
28 
29     }
30 };
31 
32 
33 /**class Solution {
34 public:
35     bool checkValidString(string s) {
36     //设两个栈,一个存左括号,一个存星号,遇右括号,先匹配左括号组,再匹配星号组,都没有返回false
37     //匹配完剩余星号和左括号匹配,注意左括号要在星号左侧,左括号不能有剩余
38     stack<int> left,star;
39 
40     for(int i=0;i<s.size();i++)//把( 和 * 放进两个两个栈里面,遇到 ),就先判断(的栈有的话,就抛出一个;如果(的栈没有,就抛出*的栈里面的一个;都为空,就返回false。
41     {
42         if(s[i]=='(')
43         {
44             left.push(i);
45         }
46         else if(s[i]=='*')
47         {
48             star.push(i);
49         }
50         else
51         {
52             if(left.empty() && star.empty())
53             {
54                 return false;
55             }
56             if(!left.empty())
57             {
58                 left.pop();
59             }
60             else
61             star.pop();
62         }
63     }
64     while(!left.empty() && !star.empty())//判断左括号和* 可能还没完全消除的情况,如果左括号在*的右边,就是返回false,因为*变成三种方式的任何一种,都不是有效字符串。
65     {
66         if(left.top()>star.top())//这是*(的情况,不是有效字符串
67         {
68             return false;
69         }
70         left.pop();
71         star.pop();
72     }
73     return left.empty();
74     }
75 };**/

方法三:从前往后和从后往前,两次遍历的方法

代码如下:

 1 class Solution {
 2 public:
 3     bool checkValidString(string s) {
 4         //两次遍历的方法
 5         int n = s.size();
 6         int left = 0;//左括号数量
 7         int right = 0;//右括号数量
 8         int star = 0;//*的数量
 9         for(int i=0;i<n;i++)//从前往后遍历,遇到)的时候,先删除(,没有(就删除* 
10         {
11             if(s[i]=='(')
12             {
13                 left++;
14             }
15             else if(s[i]=='*')
16             {
17                 star++;
18             }
19             else
20             {
21                 if(left>0)
22                 {
23                     left--;
24                 }
25                 else if(star>0)
26                 {
27                     star--;
28                 }
29                 else return false;
30             }
31         }
32         star = 0;
33         for(int i=n-1;i>=0;i--)//从后往前遍历,遇到(的时候,先删),没有)就删除* 
34         {
35             if(s[i]=='*')
36             {
37                 star++;
38             }
39             else if(s[i]==')')
40             {
41                 right++;
42             }
43             else
44             {
45                 if(right>0) right--;
46                 else if(star>0) star--;
47                 else
48                 {
49                     return false;//此时左括号没有,所以返回false
50                 }
51             }
52 
53         }
54         return true;
55     }
56 };

方法四:回溯

代码如下:

 1 class Solution {
 2 private:
 3     bool check(string& s,int start,int count)//有开始点的下标位置,有count记录未匹配的左括号的数量
 4     
 5     {
 6         if(count<0)
 7         {
 8             return false;
 9         }
10         for(int i=start;i<s.size();i++)
11         {
12             if(s[i]=='(')
13             {
14                 count++;
15             }
16             else if(s[i]==')')
17             {
18                 if(count==0)
19                 {
20                     return false;
21                 }
22                 count--;
23             }
24             else if(s[i]=='*')
25             {
26                 return check(s,i+1,count) || check(s,i+1,count+1) || check(s,i+1,count-1);
27             }
28         }
29         return count==0;
30     }
31 
32 
33 public:
34     bool checkValidString(string s) {
35         //DFS,回溯的方法
36         return check(s,0,0);    
37     }
38 };

以上代码会超时,可以用map或者vector存储start和count的值,达到优化效果,感兴趣可以试一试。

 参考链接:https://leetcode-cn.com/problems/valid-parenthesis-string/solution/fen-zhi-tan-xin-shuang-xiang-bian-li-shuang-zhan-j/

926. 将字符串翻转到单调递增

采用动态规划思想,

代码如下:

 1 class Solution {
 2 public:
 3     int minFlipsMonoIncr(string s) {
 4         //动态规划思想
 5         int n = s.size();
 6         vector<int> dp(n+1);
 7         dp[n] = 0;//dp[i]表示下标是i的时候,i~n-1之间1的数量
 8 
 9         for(int i=n-1;i>=0;i--)
10         {
11             if(s[i]=='1')
12             {
13                 dp[i]=dp[i+1]+1;
14             }
15             else
16             {
17                 dp[i]=dp[i+1];
18             }
19           //  cout<<i<<" "<<dp[i]<<endl;
20         }
21 
22         int r = INT_MAX;
23         for(int i=1;i<n;i++)
24         {
25             r = min(r,dp[0]-dp[i]+n-i-dp[i]);//dp[0]-dp[i]表示0~i-1之间1的数量,n-i-dp[i]表示i+1~n-1之间的0的数量。两者的和就是需要修改的数量
26            // cout<<r<<" "<<dp[0]-dp[i]+n-i-dp[i]<<endl;
27         }
28         int count=0;
29         for(int i=0;i<n;i++)
30         {
31             if(s[i]=='1')
32             {
33                 count++;
34             }
35         }
36         if(count<r || (n-count)<r)//修改成全0或者全1的情况,需要的次数,小于之前的修改次数的时候,可以取最小值,并返回这个最小值
37         {
38             return min(count,n-count);
39         }
40         return r;
41     }
42 };

背包问题刷题指南:

 参考链接:https://blog.csdn.net/youngyangyang04/article/details/114253921

雪儿言
原文地址:https://www.cnblogs.com/weixq351/p/15111226.html