leetcode Ch2-Dynamic Programming I

一、

1. Edit Distance

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

需要注意的细节是dp里的下标和字符串t中的下标不是一致的(Line14),是差了1的。即dp[i][j]所对应的是t1中的第i-1位和t2中的第j-1
位,即t1[i-1],t2[j-1]。因为dp中的下标代表的是对应字符串的长度。

#2: 又漏了种情况。当word1和word2末尾元素不相等时,有3种可能的操作:除dp[i-1][j]+1,dp[i][j]+1之外,还有 dp[i-1][j-1]+1。 

2. Distinct Subsequences

 1 class Solution
 2 {
 3 public:
 4     int numDistinct(string s,string t)
 5     {
 6         int sLen=s.size(),tLen=t.size();
 7         vector<vector<int>> dp(sLen+1,vector<int>(tLen+1,0));
 8         for(int i=0;i<=sLen;i++) dp[i][0]=1;
 9         for(int i=1;i<=sLen;i++)
10         {
11             for(int j=1;j<=tLen;j++)
12             {
13                 dp[i][j]=dp[i-1][j]+(s[i-1]==t[j-1]? dp[i-1][j-1]:0 );        
14             }
15         }
16         return dp[sLen][tLen];
17     }
18 };
View Code

递推式不难,关键是正确理解题意和初始化。

题目说的是T是S任意删掉若干字符得到的结果。那么当T为空时,从S中只有一种删法能够得到T(那就是删光)。所以dp[i][0]都是1. 这一步初始化很重要。

 dp[i][j]是S长度为i(从下标0开始),T长度为j(从下标0开始)所对应的删法的个数。

易出错。

#2: 忘记递推关系了; 初始化的时候是=1;

3. Scramble String

用vector,不用数组 (推荐)

 1 class Solution {
 2 public:
 3     bool isScramble(string s1, string s2) {
 4         int N = s1.size();
 5         if (N != s2.size()) {
 6             return false;
 7         }
 8         vector<vector<vector<bool>>> f(N + 1, vector<vector<bool>>(N, vector<bool>(N, false)));
 9         f[0][0][0] = true;
10         for (int i = 0; i < N; i++) {
11             for (int j = 0; j < N; j++) {
12                 f[1][i][j] = (s1[i] == s2[j]);
13             }
14         }
15         for (int n = 1; n <= N; n++) {
16             for (int i = 0; i + n <= N; i++) {
17                 for (int j = 0; j + n <= N; j++) {
18                     for (int k = 1; k < n; k++) {
19                         if ((f[k][i][j] && f[n - k][i + k][j + k]) || (f[k][i][j + n - k] && f[n - k][i + k][j])) {
20                             f[n][i][j] = true;
21                             break;
22                         }
23                     }
24                 }
25             }
26         }
27         return f[N][0][0];
28     }
29 };
View Code

用数组(不推荐)

 1 class Solution
 2 {
 3 public:
 4     bool isScramble(string s1,string s2)
 5     {
 6         const int N=s1.size();
 7         if(N!=s2.size()) return false;
 8         bool f[N+1][N][N];
 9         fill_n(&f[0][0][0],(N+1)*N*N,false);
10         for(int i=0;i<N;i++)
11             for(int j=0;j<N;j++)
12                 f[1][i][j]=(s1[i]==s2[j]);
13         for(int n=2;n<=N;n++)
14         {
15             for(int i=0;i<=N-n;i++)
16             {
17                 for(int j=0;j<=N-n;j++)
18                 {
19                     for(int k=1;k<n;k++)
20                     {
21                         if((f[k][i][j]&&f[n-k][i+k][j+k])||(f[k][i][j+n-k]&&f[n-k][i+k][j]))
22                         {
23                             f[n][i][j]=true;
24                             break;
25                         }
26                     }
27                 }
28             }
29         }
30         return f[N][0][0];
31     }
32 };
View Code
简单的说,就是s1和s2是scramble的话,那么必然存在一个在s1上的长度l1,将s1分成s11和s12两段,同样有s21和s22。
那么要么s11和s21是scramble的并且s12和s22是scramble的;要么s11和s22是scramble的并且s12和s21是scramble的。
springfor的总结不错。

这道题很多细节容易出错,要谨慎。

注意:这个代码在VS下通不过,虽然能过OJ。

#2: 忘记对 f[1][i][j] 的初始化了。而且写的巨慢。

注意:k是从1到n-1的,不能从0开始,那会导致越界。

二、最长递增子序列 LIS

见此整理,三。'

#2: 老是重复犯一个错误!!!每次应用binarySearch的时候都用到了nums上,应该是用到存储当前位置最小元素的数组B上!!!

三、矩阵链乘

 1 int matrixChain(vector<pair<int,int>> &vp)
 2 {
 3     int len = vp.size();
 4     vector<vector<int>> dp(SIZE, vector<int>(SIZE, INT_MAX));
 5     for (int i = 0; i<len; i++) dp[i][i] = 0;
 6     for (int L = 2; L <= len; L++)
 7     {
 8         for (int i = 0; i <= len - L; i++)
 9         {
10             int j = i + L - 1;
11             int minV = INT_MAX;
12             for (int k = i; k < j; k++)
13             {
14                 if (dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second<minV)
15                     minV = dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second;
16             }
17             dp[i][j] = minV;
18         }
19     }
20     return dp[0][len - 1];
21 }
View Code

需要注意的是Line12,k<j,不能是k<=j. 因为我们的划分是划分成[i...k][k+1...j],所以对于后半段需要满足k+1<=j,即k必须小于j。

完整代码:

不能首尾相连版:

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 #include <climits>
 5 using namespace std;
 6 
 7 #define SIZE 1000
 8 void init(vector<pair<int, int>> &vp)
 9 {
10     int n;
11     cin >> n;
12     while (n--)
13     {
14         int x, y;
15         cin >> x >> y;
16         vp.push_back(make_pair(x, y));
17     }
18 }
19 
20 int matrixChain(vector<pair<int,int>> &vp)
21 {
22     int len = vp.size();
23     vector<vector<int>> dp(SIZE, vector<int>(SIZE, INT_MAX));
24     for (int i = 0; i<len; i++) dp[i][i] = 0;
25     for (int L = 2; L <= len; L++)
26     {
27         for (int i = 0; i <= len - L; i++)
28         {
29             int j = i + L - 1;
30             int minV = INT_MAX;
31             for (int k = i; k < j; k++)
32             {
33                 if (dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second<minV)
34                     minV = dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second;
35             }
36             dp[i][j] = minV;
37         }
38     }
39     return dp[0][len - 1];
40 }
41 
42 int main()
43 {
44     vector<pair<int, int>> vp;
45     init(vp);
46     cout << matrixChain(vp) << endl;
47 }
View Code

可以首尾相连版:

 1 #include <iostream>
 2 #include <vector>
 3 #include <algorithm>
 4 #include <climits>
 5 using namespace std;
 6 
 7 struct mat {
 8     int rowNum;
 9     int colNum;
10 };
11 
12 void init(vector<mat> &v) {
13     int n;
14     cin >> n;
15     while (n--) {
16         mat tmp;
17         cin >> tmp.rowNum >> tmp.colNum;
18         v.push_back(tmp);
19     }
20 }
21 
22 int matMultiply(vector<mat> &v) {
23     int len = v.size();
24     if (len == 0) {
25         return 0;
26     }
27     vector<vector<int> > dp(2 * len, vector<int>(2 * len, INT_MAX));
28     for (int i = 0; i < 2 * len; i++) {
29         dp[i][i] = 0;
30     }
31     int minV = INT_MAX;
32     for (int L = 2; L <= len; L++) {
33         for (int i = 0; i <= 2 * len - L; i++) {
34             int j = i + L - 1;
35             for (int k = i; k < j; k++) {
36                 int tmp = dp[i][k] + dp[k + 1][j] + v[i % len].rowNum * v[k % len].colNum * v[j % len].colNum;
37                 dp[i][j] = min(dp[i][j], tmp);
38             }
39             if (L == len) {
40                 minV = min(minV, dp[i][j]);
41             }
42         }
43     }
44     return minV;
45 }
46 
47 int main() {
48     vector<mat> v;
49     init(v);
50     cout << matMultiply(v) << endl;
51     return 0;
52 }
View Code

 reference

#2: 犯的错误:if(L == len) 放错位置,放在了i循环外面。

 

四、最长公共子序列 LCS

 1 #include <iostream>
 2 #include <vector>
 3 #include <string>
 4 using namespace std;
 5 
 6 int lcs(string t1, string t2, vector<vector<int>> &trace)
 7 {
 8     int len1 = t1.size(), len2 = t2.size();
 9     vector<vector<int>> dp(t1.size() + 1, vector<int>(t2.size() + 1, 0));
10     for (int i = 1; i <= len1; i++)
11     {
12         for (int j = 1; j <= len2; j++)
13         {
14             if (t1[i - 1] == t2[j - 1])
15             {
16                 dp[i][j] = dp[i - 1][j - 1] + 1;
17                 trace[i][j] = 0;
18             }
19             else
20             {
21                 if (dp[i - 1][j]>dp[i][j - 1])
22                 {
23                     dp[i][j] = dp[i - 1][j];
24                     trace[i][j] = 1;
25                 }
26                 else
27                 {
28                     dp[i][j] = dp[i][j - 1];
29                     trace[i][j] = 2;
30                 }
31             }
32         }
33     }
34     return dp[len1][len2];
35 }
36 
37 void print(vector<vector<int>> &trace, int i, int j, string t1)
38 {
39     if (i == 0 || j == 0) return;
40     if (trace[i][j] == 0)
41     {
42         print(trace, i - 1, j - 1,t1);
43         cout << t1[i-1];
44     }
45     else if (trace[i][j] == 1)
46         print(trace, i - 1, j, t1);
47     else if(trace[i][j]==2)
48         print(trace, i, j - 1, t1);
49 }
50 
51 int main()
52 {
53     string t1;
54     string t2;
55     cin >> t1 >> t2;
56     vector<vector<int>> trace(t1.size()+1, vector<int>(t2.size()+1, -1));
57     lcs(t1, t2, trace);
58     print(trace, t1.size(), t2.size(), t1);
59     //cout << endl;
60 }
View Code

这里仍然是需要注意下标问题。dp[i][j]里的下标仍然代表的是长度,即对应t1[i-1],t2[j-1]. 不过trace的下标和dp一样,也是长度。这里构造出LCS序列时,可以如代码中那样利用递归,也可以while(dp[i][j]) if(dp[i][j]==dp[i-1][j]) i--; ........... 用while循环来做。(refer to bnuzhanyu in 51nod)

reference and 算法概论,算法导论

#2: 忘记了怎么递归地output。

 

五、

1. 最长重复子串 Longest Repeat Substring (LRS)

最长重复子串是单字符串问题。本题中默认为允许重叠。

直观做法 O(n^3)

 1 #include <string>
 2 #include <vector>
 3 #include <iostream>
 4 using namespace std;
 5 
 6 int comp(string str, int i, int j)
 7 {
 8     int len = 0;
 9     while (i<str.size() && j<str.size() && str[i] == str[j])
10     {
11         len++;
12         i++; j++;
13     }
14     return len;
15 }
16 int lrs(string str,int &maxIndex)
17 {
18     int maxV = 0;
19     for (int i = 0; i<str.size(); i++)
20     {
21         for (int j = i + 1; j<str.size(); j++)
22         {
23             int len = comp(str, i, j);
24             if (len>maxV) {
25                 maxV = len;
26                 maxIndex = i;
27             }
28         }
29     }
30     return maxV;
31 }
32 
33 void output(string str, int maxIndex,int len)
34 {
35     while (len--)
36     {
37         cout << str[maxIndex++];
38     }
39 }
40 
41 int main()
42 {
43     string str("banana");
44     int maxIndex = 0;
45     int len= lrs(str,maxIndex);
46     output(str,maxIndex,len);
47     return 0;
48 }
View Code

refer1

refer2

后缀数组做法:

 

最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和

 

2. Longest Substring Without Repeating Characters [最长不重复子串]

 1 class Solution {
 2 public:
 3     int lengthOfLongestSubstring(string str) {
 4         vector<bool> exist(256, false);
 5         int n = str.size();
 6         int i = 0, j = 0;
 7         int maxlen = 0;
 8         while (j < n) {
 9             if (exist[str[j]]) {
10                 maxlen = max(maxlen, j - i);
11                 while (str[i] != str[j]) {
12                     exist[str[i]] = false;
13                     i++;    
14                 }
15                 i++;
16                 j++;
17             } else {
18                 exist[str[j]] = true;
19                 j++;
20             }
21         }
22         maxlen = max(maxlen, n - i);
23         return maxlen;
24     }
25 };
View Code

ref

 

六、

1. Unique Binary Search Trees

 1 class Solution
 2 {
 3 public:
 4     int numTrees(int n)
 5     {
 6         vector<int> g(n+1,0);
 7         g[0]=g[1]=1;
 8         for(int i=2;i<=n;i++)
 9         {
10             for(int j=0;j<i;j++)
11             {
12                 g[i]+=g[j]*g[i-1-j];
13             }
14         }
15         return g[n];
16     }
17 };
View Code

g[n]表示对于长为n的序列能有多少个UniqueBST。循环里的 i 表示序列长度,依次从短向长计算,直到计算出最终结果g[n].

g[n]=g[0]*g[n-1]+g[1]*g[n-2]+...+g[n-1]*g[0]

ref 解释的非常清楚。

2. Unique Binary Search Trees II

 1 class Solution
 2 {
 3 public:
 4     vector<TreeNode*> generateTrees(int n)
 5     {
 6         return dfs(1,n);
 7     }
 8     vector<TreeNode*> dfs(int min,int max)
 9     {
10         vector<TreeNode*> res;
11         if(min>max) 
12         {
13             res.push_back(NULL);//表示空树,不可缺少。否则会影响下面对leftSub,rightSub的循环。
14             return res;
15         }
16         for(int i=min;i<=max;i++)
17         {
18             vector<TreeNode*> leftSub=dfs(min,i-1);
19             vector<TreeNode*> rightSub=dfs(i+1,max);
20             for(int j=0;j<leftSub.size();j++)
21             {
22                 for(int k=0;k<rightSub.size();k++)
23                 {
24                     TreeNode* root=new TreeNode(i);
25                     root->left=leftSub[j];
26                     root->right=rightSub[k];
27                     res.push_back(root);
28                 }
29             }
30         }
31         return res;
32     }
33 };
View Code

对于Line11-15,当min>max时表示空树。空树必须要加进去一个NULL来表示出来,否则,对于下面分别从leftSub和rightSub取出一个元素组成两个子树时,如果一边(假设为leftSub)为空且没有表示出空树来的话,这一层for循环就不能执行了,那对于有n-1个节点的rightSub的各种形态都没法执行循环列出来了。整个双层for循环都没法用了。

ref

#2: 想不出怎么实现的了。而且Line24如果放在两层循环的外面是会WA的,还没明白为什么。

七、

1. Maximum Subarray 最大子数组和

code1: [美观]

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

code2:[不够美观]

 1 class Solution
 2 {
 3 public:
 4     int maxSubArray(vector<int> &nums)
 5     {
 6         int sum=0;
 7         int maxV=INT_MIN;
 8         for(int i=0;i<nums.size();i++)
 9         {
10             if(sum<0) sum=0;
11             sum+=nums[i];
12             if(sum>maxV) maxV=sum;
13         }
14         return maxV;
15     }
16 };
View Code

采用dp来做的话,dp[n]代表从arr[0]到arr[n]的最大子数组和。它的子问题dp[n-1]是从arr[0]到arr[n-1]的最大子数组和。注意,起点始终是arr[0].

看一下july列出的扩展问题以及勇幸的整理

  1. 如果数组是二维数组,同样要你求最大子数组的和列?
  2. 如果是要你求子数组的最大乘积列?
  3. 如果同时要求输出子段的开始和结束列?

另外,关于如何用divide and conquer来解此问题,详见此贴

2. 环形最大连续数组和

 1 int maxConsSum(const vector<int> &arr)
 2 {
 3     int dp=0,res=0;
 4     for(int i=0;i<arr.size();i++)
 5     {
 6         dp=max(arr[i],dp+arr[i]);
 7         res=max(res,dp);
 8     }
 9     return res;
10 }
11 
12 int minConsSum(const vector<int> &arr)
13 {
14     int dp=0,res=0;
15     for(int i=0;i<arr.size();i++)
16     {
17         dp=min(arr[i],dp+arr[i]);
18         res=min(dp,res);
19     }
20     return res;
21 }
22 
23 
24 int maxConsSum2(const vector<int> &arr) {
25     int sum=0;
26     for(int i=0;i<arr.size();i++)
27         sum+=arr[i];
28     int m1=maxConsSum(arr);
29     int m2=minConsSum(arr);
30     int res=max(m1,sum-m2);
31     return res;
32 }
View Code

ref1   ref2

一开始的思路和ref2里刚开始说的那样,想着首尾拼接起来成一个长数组。但是貌似需要O(n*n)才行(难道是列举出长度为arr.size()的这n个数组,分别对其执行最大子段和MaxSubArray的O(n)算法?还没进行实现。)后来参考了ref2和ref1的代码,如果跨越了末尾就用(数组元素之和-最小子段和),如果没跨越就用(最大子段和)。对其取max即可。

注意一个与上题不同的细节。这题里说可以允许有空段,而上题里要求是至少含有一个元素。因此res的初值一个为INT_MIN,一个为0.

另外,再看一下ref3提到的几个拓展问题

可以理解为:若环形最大子段和需要跨越首尾,那么最小子段和必然不需要跨越。举个极端的例子就是,首尾元素都是正数,其余元素都是负数。

 

八、Climbing Stairs

空间复杂度O(n):【非最优】

 1 class Solution
 2 {
 3 public:
 4     int climbStairs(int n)
 5     {
 6         vector<int> dp(n+1,0);
 7         dp[0]=dp[1]=1;
 8         for(int i=2;i<=n;i++)
 9         {
10             dp[i]=dp[i-1]+dp[i-2];
11         }
12         return dp[n];
13     }
14 };
View Code

若不能开辟数组的话。

空间复杂度O(1):

 1 class Solution
 2 {
 3 public:
 4     int climbStairs(int n)
 5     {
 6         int prev=0,curr=1;
 7         int tmp=0;
 8         for(int i=1;i<=n;i++)
 9         {
10             tmp=curr;
11             curr+=prev;
12             prev=tmp;        
13         }
14         return curr;
15     }
16 };
View Code

使用3个变量即可。和Fibonacci一样。

 

九、

1. Unique Paths

空间复杂度O(mn) 【非最优】

 1 class Solution
 2 {
 3 public:
 4     int uniquePaths(int m,int n)
 5     {
 6         vector<vector<int>> dp(m,vector<int>(n,0));
 7         for(int i=0;i<m;i++)
 8             dp[i][0]=1;
 9         for(int i=0;i<n;i++)
10             dp[0][i]=1;
11         for(int i=1;i<m;i++)
12         {
13             for(int j=1;j<n;j++)
14             {
15                 dp[i][j]=dp[i-1][j]+dp[i][j-1];
16             }
17         }
18         return dp[m-1][n-1];
19     }
20 };
View Code

空间复杂度O(n) 【非最优】

 1 class Solution
 2 {
 3 public:
 4     int uniquePaths(int m,int n)
 5     {
 6         vector<int> dp(n,1);
 7         for(int i=1;i<m;i++)
 8         {
 9             for(int j=1;j<n;j++)
10             {
11                 dp[j]=dp[j]+dp[j-1];
12             }
13         }
14         return dp[n-1];
15     }
16 };
View Code

等式左边的dp[j]代表dp[i][j],等式右边的dp[j]代表dp[i-1][j]. 而dp[j-1]代表dp[i][j-1].

可参考 ref1  ref2 学习一下滚动数组。

 利用公式 【最优】

 1 class Solution
 2 {
 3 public:
 4     int uniquePaths(int m,int n)
 5     {
 6         int N=m+n-2;
 7         int k=min(n-1,m-1);
 8         double res=1.0;
 9         for(int i=1;i<=k;i++)
10             res*=(double)(N-i+1)/i;
11         return round(res);                    
12     }
13 };
View Code

注意:Line10必须加double,否则等号右边精度就丢失了;Line11不能用(int),要用round,这样得到的结果还能四舍五入到准确值,否则用int的话有可能就和准确结果差了1

ps: round实现如下:

double round(double number)
{
    return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5);
}
View Code

ref      绝对严格的准确解参考soulmach。

2. Unique Paths II

 1 class Solution
 2 {
 3 public:
 4     int uniquePathsWithObstacles(vector<vector<int>> &obstacleGrid)
 5     {
 6         if(obstacleGrid.empty()) return 0;
 7         int m=obstacleGrid.size();
 8         int n=obstacleGrid[0].size();
 9         vector<vector<int>> dp(m,vector<int>(n,0));
10         for(int i=0;i<m;i++)
11         {
12             if(obstacleGrid[i][0]==1)
13                 break;
14             dp[i][0]=1;
15         }
16         for(int i=0;i<n;i++)
17         {
18             if(obstacleGrid[0][i]==1)
19                 break;
20             dp[0][i]=1;
21         }
22         for(int i=1;i<m;i++)
23         {
24             for(int j=1;j<n;j++)
25             {
26                 if(obstacleGrid[i][j]==1)
27                     dp[i][j]=0;
28                 else
29                     dp[i][j]=dp[i-1][j]+dp[i][j-1];
30             }
31         }
32         return dp[m-1][n-1];
33     }
34 };
View Code

注意Line22,24,一定要从1开始。从0开始就错了,因为后面需要递推i-1,j-1。

滚动数组法节省空间:

3. Minimum Path Sum

 1 class Solution
 2 {
 3 public:
 4     int minPathSum(vector<vector<int>> &grid)
 5     {
 6         if(grid.empty()) return 0;
 7         int m=grid.size(),n=grid[0].size();
 8         vector<vector<int>> dp(m,vector<int>(n,0));
 9         dp[0][0]=grid[0][0];
10         for(int i=1;i<m;i++)
11             dp[i][0]=dp[i-1][0]+grid[i][0];
12         for(int i=1;i<n;i++)
13             dp[0][i]=dp[0][i-1]+grid[0][i];
14         for(int i=1;i<m;i++)
15         {
16             for(int j=1;j<n;j++)
17             {
18                 dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
19             }
20         }
21         return dp[m-1][n-1];
22     }
23 };
View Code

4. Dungeon Game

 1 class Solution
 2 {
 3 public:
 4     int calculateMinimumHP(vector<vector<int>> &dungeon)
 5     {
 6         if(dungeon.empty()) return 1;
 7         int m=dungeon.size(),n=dungeon[0].size();
 8         vector<vector<int>> dp(m,vector<int>(n,0));
 9         dp[m-1][n-1]=max(1,1-dungeon[m-1][n-1]);
10         for(int i=m-2;i>=0;i--)
11             dp[i][n-1]=max(1,dp[i+1][n-1]-dungeon[i][n-1]);
12         for(int i=n-2;i>=0;i--)
13             dp[m-1][i]=max(1,dp[m-1][i+1]-dungeon[m-1][i]);
14         for(int i=m-2;i>=0;i--)
15             for(int j=n-2;j>=0;j--)
16                 dp[i][j]=max(1,min(dp[i+1][j],dp[i][j+1])-dungeon[i][j]);
17         return dp[0][0];
18     }
19 };
View Code

dp[i][j]: 在走进dungeon[i][j]之前至少应有的hp值。

一开始自己写的太罗嗦了,一堆判断语句,结果其实就用一句max(1,dp[][]-dungeon[][])就行了。

 ref    ref2

#2: 特殊处理的行和列分别是 m-1行和 n-1列, 不是0!

此外,递推出错。最重要的递推公式是max(1, min(.....)), 别想当然。

十、

1. Best Time to Buy and Sell Stock

code1:

 1 class Solution
 2 {
 3 public:
 4     int maxProfit(vector<int>& prices)
 5     {
 6         if(prices.size()<2) return 0;
 7         int dp=0,minV=prices[0];
 8         for(int i=1;i<prices.size();i++)
 9         {    
10             dp=max(dp,prices[i]-minV);
11             minV=min(minV,prices[i]);
12         }
13         return dp;
14     }
15 }; 
View Code

code2:

 1 class Solution
 2 {
 3 public:
 4     int maxProfit(vector<int> &prices)
 5     {
 6         if(prices.size()==0) return 0;
 7         int min=prices[0];
 8         int res=0;
 9         for(int i=1;i<prices.size();i++)
10         {
11             if(prices[i]<min) min=prices[i];
12             if(prices[i]-min>res) res=prices[i]-min;
13         }
14         return res;
15     }
16 };
View Code

和最大连续子数组和类似。子问题都是以当前位置作为结尾(卖出点)时的最优解。取这N个最优解中的最大值。但是MaxSubArray里是一定包括当前位置的,本题里不一定包括当前位置,只是指定一个范围。

2. Best Time to Buy and Sell Stock III

 1 class Solution
 2 {
 3 public:
 4     int maxProfit(vector<int> &prices)
 5     {
 6         if(prices.size()<2) return 0;
 7         const int n=prices.size();
 8         vector<int> f(n,0);
 9         vector<int> g(n,0);
10         int minV=prices[0],maxV=prices[n-1];
11         for(int i=1;i<n;i++)
12         {
13             f[i]=max(f[i-1],prices[i]-minV);
14             minV=min(minV,prices[i]);
15         }
16         for(int i=n-2;i>=0;i--)
17         {
18             g[i]=max(g[i+1],maxV-prices[i]);
19             maxV=max(maxV,prices[i]);
20         }
21         int res=0;
22         for(int i=0;i<n;i++)
23         {
24             res=max(f[i]+g[i],res);
25         }
26         return res;
27     }
28 };
View Code

f[i]是记录[0..i]中的最大收益,是从前向后算的;g[i]是记录[i..n-1]中的最大收益,是从后向前算的。

【注意】不要忘了第一句prices为空时的操作!细节很重要!pay attention to corner case !

ref:soulMach.

 

本题是最多允许两次交易。看看扩展到最多k次交易的一般情况。reference    【最大M子段和

3.  Best Time to Buy and Sell Stock IV

 

ref1      ref2       ref3       ref4  

十一、 

1. House Robber

方法一:O(n) space

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

递推公式:dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])

其中这里的i指的是从下标0开始的长为i的序列能rob到的最大值。注意长度为i的序列对应的末尾元素应是nums[i-1],别忘了减1.

之前感觉递推公式不太对,因为毕竟这里的dp[i]是表示的从0到下标i这个区间范围内所能取到的最大值,不一定包含末尾元素。这样的话dp[i-2]就有可能不包含nums[i - 3],而是以nums[i - 4]结尾,因此在它的基础上可以加上nums[i - 2],并在这种方案下取到最大值。但是后来想明白了,其实这种情况是已经包含在dp[i-1]这种情况里了。dp[i-1]所不能涵盖的就是dp[i-2]+nums[i-1]这种情况。因此最终在dp[i-1]和dp[i-2]+nums[i-1]之间取个较大值即可。

总结起来就是,dp[i]的大多数情况都能被dp[i-1]所代替。dp[i-1]唯一代替不了的方案是以nums[i-1]为结尾元素的方案,即dp[i-2] + nums[i-1]. 将这两大方案取个较大值即可。

ref

#2: dp[i]里的i是表示的长度。所以开辟的vector是n+1大小的。但是在line11处却用的是i < n. 应该是 i <= n.

方法二:O(1) space

 1 class Solution
 2 {
 3 public:
 4     int rob(vector<int> &nums)
 5     {
 6         if(nums.size()==0) return 0;
 7         int odd=0,even=0;
 8         for(int i=0;i<nums.size();i++)
 9         {
10             if(i%2==0)
11                 even=max(odd,even+nums[i]);
12             else
13                 odd=max(even,odd+nums[i]);
14         }
15         return even>odd?even:odd;
16     }
17 };
View Code

odd是之前在奇数位达到的最大值,even是之前在偶数位达到的最大值。

follow up: 如果首尾相连呢?

2. House Robber II

code 1:

 1 class Solution
 2 {
 3 public:
 4     int rob(vector<int> &nums)
 5     {
 6         if(nums.size()==0) return 0;
 7         if(nums.size()==1) return nums[0];
 8         int odd=0,even=0;
 9         for(int i=0;i<nums.size()-1;i++)
10         {        
11             if(i%2==0)
12                 even=max(odd,even+nums[i]);
13             else
14                 odd=max(even,odd+nums[i]);
15         }
16         int res1=max(even,odd);
17         even=odd=0;
18         for(int i=1;i<nums.size();i++)
19         {        
20             if(i%2==0)
21                 even=max(odd,even+nums[i]);
22             else
23                 odd=max(even,odd+nums[i]);
24         }
25         int res2=max(even,odd);
26         return max(res1,res2);
27     }
28 };
View Code

code 2:

 1 class Solution {
 2 public:
 3     int rob(vector<int> &nums) {
 4         int n = nums.size();
 5         if (n < 1) {
 6             return 0;
 7         }
 8         if (n == 1) {
 9             return nums[0];
10         }
11         vector<int> dp(n + 1, 0);
12         int res1 = 0, res2 = 0;
13         dp[1] = nums[0];
14         for (int i = 2; i < n; i++) {
15             dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
16         }
17         res1 = dp[n - 1];
18         fill_n(dp.begin(), dp.size(), 0);
19         dp[1] = nums[1];
20         for (int i = 2; i < n; i++) {
21             dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
22         }
23         res2 = dp[n - 1];
24         return max(res1, res2);
25     }
26 };
View Code

如果首尾相连,其实就意味着首和尾最多只能有1个(也可能都没有,反正不能同时出现)。所以就对上一题的算法跑两遍,一遍去掉头,一遍去掉尾。取这两次结果的较大值即可。

十二、 Triangle

 1 class Solution
 2 {
 3 public:
 4     int minimumTotal(vector<vector<int>> &triangle)
 5     {
 6         int n=triangle.size();
 7         vector<vector<int>> dp(n,vector<int>(n,0));
 8         for(int i=0;i<=n-1;i++)
 9             dp[n-1][i]=triangle[n-1][i];
10         for(int i=n-2;i>=0;i--)
11         {
12             for(int j=0;j<=i;j++)
13             {
14                 dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j];
15             }
16         }
17         return dp[0][0];
18     }
19 };
View Code

 从下向上计算确实比从上向下要简单很多,没有额外的边界条件需要考虑。

滚动数组版: 

十三、

1. Word Break

先来个dfs版的。一阵子不练dfs就生疏了。

 1 class Solution
 2 {
 3 public:
 4     bool wordBreak(string s, unordered_set<string> &wordDict)
 5     {
 6         return dfs(s, wordDict, 0);
 7     }
 8     bool dfs(string s, unordered_set<string> &wordDict, int start)
 9     {
10         if (start == s.size())
11             return true;
12         for (int i = start; i<s.size(); i++)
13         {
14             if (wordDict.count(s.substr(start, i - start + 1))>0)
15             {
16                 if (dfs(s, wordDict, i + 1))
17                     return true;
18             }
19         }
20         return false;
21     }
22 };
View Code

DP:

 1 class Solution
 2 {
 3 public:
 4     bool wordBreak(string s, unordered_set<string> &wordDict)
 5     {
 6         vector<bool> dp(s.size()+1,0);
 7         dp[0]=1;
 8         for(int i=1;i<=s.size();i++)
 9         {
10             for(int j=0;j<i;j++)    
11             {
12                 if(dp[j]&&wordDict.count(s.substr(j,i-j))>0)
13                 {
14                     dp[i]=true;
15                     break;
16                 }
17             }
18         }
19         return dp[s.size()];
20     }
21 };
View Code

i表示当前子串的长度(从下标0开始),j表示此时把子串分割成的左半段的长度。dp[i]就表示从0开始长度为i的子串是不是满足wordBreak的。

ref     自底向上,由小到大的构造出来。

2. Word Break II

常规dfs版:【TLE】

 1 class Solution
 2 {
 3 public:
 4     vector<string> wordBreak(string s,unordered_set<string> &wordDict)
 5     {
 6         dfs(s,wordDict,0);
 7         return result;
 8     }
 9     vector<string> result;
10     string path;
11     void dfs(string s,unordered_set<string> &wordDict,int start)
12     {
13         if(start==s.size())    
14         {
15             result.push_back(path);
16             return;
17         }
18         for(int i=start;i<s.size();i++)
19         {
20             if(wordDict.count(s.substr(start,i-start+1))>0)
21             {
22                 if(!path.empty()) path+=" ";
23                 path+=s.substr(start,i-start+1);
24                 dfs(s,wordDict,i+1);
25                 path.resize(path.size()-(i-start+1));
26                 if(!path.empty()) path.resize(path.size()-1);
27             }
28         }
29     }
30 };
View Code

改进版dfs--dp剪枝版:(除了利用word break里的函数外,只比上面的 [常规dfs版] 多了line6这一句话)

 1 class Solution
 2 {
 3 public:
 4     vector<string> wordBreak(string s,unordered_set<string> &wordDict)
 5     {
 6         if(!isWordBreak(s,wordDict)) return result;
 7         dfs(s,wordDict,0);
 8         return result;
 9     }
10 private:
11     vector<string> result;
12     string path;
13     void dfs(string s,unordered_set<string> &wordDict,int start)
14     {
15         if(start==s.size())    
16         {
17             result.push_back(path);
18             return;
19         }
20         for(int i=start;i<s.size();i++)
21         {
22             if(wordDict.count(s.substr(start,i-start+1))>0)
23             {
24                 if(!path.empty()) path+=" ";
25                 path+=s.substr(start,i-start+1);
26                 dfs(s,wordDict,i+1);
27                 path.resize(path.size()-(i-start+1));
28                 if(!path.empty()) path.resize(path.size()-1);
29             }
30         }
31     }
32     
33     bool isWordBreak(string s, unordered_set<string> &wordDict)
34     {
35         vector<bool> dp(s.size()+1,0);
36         dp[0]=1;
37         for(int i=1;i<=s.size();i++)
38         {
39             for(int j=0;j<i;j++)    
40             {
41                 if(dp[j]&&wordDict.count(s.substr(j,i-j))>0)
42                 {
43                     dp[i]=true;
44                     break;
45                 }
46             }
47         }
48         return dp[s.size()];
49     }
50 };
View Code

参考该博客,dp的作用只有1行,line6处。

纯dp版:

 

refer to discuss

 

十四、

1. Largest Rectangle in Histogram

 1 class Solution
 2 {
 3 public:
 4     int largestRectangleArea(vector<int> &height)
 5     {
 6         stack<int> s;
 7         height.push_back(0);
 8         int i=0,res=0;
 9         while(i<height.size())
10         {
11             if(s.empty()||height[i]>=height[s.top()])
12                 s.push(i++);
13             else
14             {
15                 int tmp=s.top();
16                 s.pop();
17                 res=max(res,height[tmp]*(s.empty()?i:i-s.top()-1));
18             }
19         }
20         return res;
21     }
22 };
View Code

 关键是对于一个特定的柱体,如何最大限度的向左右扩展。

首先看向右扩展。假设当前需要出栈的柱体的下标是x,那么当它要出栈时,说明当前遍历到的下标i对应的柱体高度h[i]小于h[x].(其实也说明了下标i之前的柱体高度都是大于h[x]的) 所以当前柱体向右扩展最多能扩展到i的前一个,即下标i-1对应的柱体。

下面看向左扩展。从当前要出栈的柱体x开始向左依次找,第一个高度小于h[x]的柱体即为向左扩展的左边界(不包含)。而第一个高度小于h[x]的柱体恰恰是栈中紧挨着它的那个,即把柱体x出栈之后的栈顶元素s.top()(不包含)。特例:若此时栈已经空了,那么左边界就是下标0(包含)。

所以,当栈非空时,柱体x向左扩展的边界是s.top()+1(包含),向右扩展的边界是i-1(包含),介于这两个之间共有 i-s.top()-1 个柱体。

     当栈为空时,向左扩展的边界是0(包含),右边界不变,共有 个柱体。

ref

#2: Line11 忘了加上 if(s.empty()) 这个判断。这会导致RE. Line17也忘了考虑s.empty();

 

2. Maximal Rectangle

 1 class Solution
 2 {
 3 public:
 4     int maximalRectangle(vector<vector<char>> &matrix)
 5     {
 6         if(matrix.empty()) return 0;
 7         int m=matrix.size(),n=matrix[0].size();
 8         vector<int> height(n,0);
 9         int res=0;
10         for(int i=0;i<m;i++)
11         {
12             for(int j=0;j<n;j++)
13             {
14                 if(matrix[i][j]=='0')
15                     height[j]=0;
16                 else
17                     height[j]+=1;
18             }
19             res=max(res,largestRectangleArea(height));
20         }
21         return res;
22     }
23 
24     int largestRectangleArea(vector<int> &height)
25     {
26         stack<int> s;
27         height.push_back(0);
28         int i=0,res=0;
29         while(i<height.size())
30         {
31             if(s.empty()||height[i]>=height[s.top()])
32                 s.push(i++);
33             else
34             {
35                 int tmp=s.top();
36                 s.pop();
37                 res=max(res,height[tmp]*(s.empty()?i:i-s.top()-1));
38             }
39         }
40         return res;
41     }
42 };
View Code

先预处理再做,是一个很好的技巧。

学以致用。预处理出高度来,再利用上一题的代码。

ref

3. Trapping Rain Water

 1 class Solution
 2 {
 3 public:
 4     int trap(vector<int> &height)
 5     {
 6         int n=height.size();
 7         if(n==0) return 0;
 8         vector<int> leftMost(n,0);
 9         vector<int> rightMost(n,0);
10         int maxV=0,res=0;
11         for(int i=0;i<n;i++)
12         {
13             leftMost[i]=maxV;
14             maxV=max(maxV,height[i]);    
15         }
16         maxV=height[n-1];
17         for(int i=n-1;i>=0;i--)
18         {
19             rightMost[i]=maxV;
20             maxV=max(maxV,height[i]);
21         }
22         for(int i=0;i<n;i++)
23         {
24             if(min(leftMost[i],rightMost[i])>height[i])
25                 res+=min(leftMost[i],rightMost[i])-height[i];
26         }
27         return res;        
28     }
29 };
View Code

规律:对每个小柱子X而言,它头顶上会不会有水取决于它左边所有柱子里最高的那个柱子A以及它右边所有柱子里最高的那个柱子B。A和B较矮的那个如果高过当前这个柱子X,那么这个小柱子X头顶上就能有 min(height[A],height[B]) - height[X] 这么高的水。

时间O(n),空间O(n)。

注意对n==0的边界条件的判断。【corner case !】

ref1     ref2

学习下空间复杂度为O(1)的解法。   ref1     ref2      ref3

4. Container With Most Water

 1 class Solution
 2 {
 3 public:
 4     int maxArea(vector<int> &height)
 5     {
 6         if(height.empty()) return 0;
 7         int p1=0,p2=height.size()-1;
 8         int res=0;
 9         while(p1<=p2)
10         {
11             if(height[p1]<height[p2])
12             {
13                 res=max(res,height[p1]*(p2-p1));
14                 p1++;
15             }
16             else
17             {
18                 res=max(res,height[p2]*(p2-p1));
19                 p2--;
20             }
21         }
22         return res;
23     }
24 };
View Code

比较左右两板,对于较短的板而言,无论另一块板(较长的板)如何向中间移动,都不可能取的比现在更大的面积。因此记录下此时的面积,将短板向中间移动。

 

转载于:https://www.cnblogs.com/forcheryl/p/4534973.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值