一、
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 };
需要注意的细节是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。
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 };
递推式不难,关键是正确理解题意和初始化。
题目说的是T是S任意删掉若干字符得到的结果。那么当T为空时,从S中只有一种删法能够得到T(那就是删光)。所以dp[i][0]都是1. 这一步初始化很重要。
dp[i][j]是S长度为i(从下标0开始),T长度为j(从下标0开始)所对应的删法的个数。
易出错。
#2: 忘记递推关系了; 初始化的时候是=1;
用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 };
用数组(不推荐)
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 };
这道题很多细节容易出错,要谨慎。
注意:这个代码在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 }
需要注意的是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 }
可以首尾相连版:
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 }
#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 }
这里仍然是需要注意下标问题。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 }
后缀数组做法:
最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和
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 };
六、
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 };
g[n]表示对于长为n的序列能有多少个UniqueBST。循环里的 i 表示序列长度,依次从短向长计算,直到计算出最终结果g[n].
g[n]=g[0]*g[n-1]+g[1]*g[n-2]+...+g[n-1]*g[0]
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 };
对于Line11-15,当min>max时表示空树。空树必须要加进去一个NULL来表示出来,否则,对于下面分别从leftSub和rightSub取出一个元素组成两个子树时,如果一边(假设为leftSub)为空且没有表示出空树来的话,这一层for循环就不能执行了,那对于有n-1个节点的rightSub的各种形态都没法执行循环列出来了。整个双层for循环都没法用了。
#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 };
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 };
采用dp来做的话,dp[n]代表从arr[0]到arr[n]的最大子数组和。它的子问题dp[n-1]是从arr[0]到arr[n-1]的最大子数组和。注意,起点始终是arr[0].
看一下july列出的扩展问题。以及勇幸的整理。
- 如果数组是二维数组,同样要你求最大子数组的和列?
- 如果是要你求子数组的最大乘积列?
- 如果同时要求输出子段的开始和结束列?
另外,关于如何用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 }
一开始的思路和ref2里刚开始说的那样,想着首尾拼接起来成一个长数组。但是貌似需要O(n*n)才行(难道是列举出长度为arr.size()的这n个数组,分别对其执行最大子段和MaxSubArray的O(n)算法?还没进行实现。)后来参考了ref2和ref1的代码,如果跨越了末尾就用(数组元素之和-最小子段和),如果没跨越就用(最大子段和)。对其取max即可。
注意一个与上题不同的细节。这题里说可以允许有空段,而上题里要求是至少含有一个元素。因此res的初值一个为INT_MIN,一个为0.
另外,再看一下ref3提到的几个拓展问题。
可以理解为:若环形最大子段和需要跨越首尾,那么最小子段和必然不需要跨越。举个极端的例子就是,首尾元素都是正数,其余元素都是负数。
空间复杂度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 };
若不能开辟数组的话。
空间复杂度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 };
使用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 };
空间复杂度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 };
等式左边的dp[j]代表dp[i][j],等式右边的dp[j]代表dp[i-1][j]. 而dp[j-1]代表dp[i][j-1].
利用公式 【最优】
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 };
注意: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); }
ref 绝对严格的准确解参考soulmach。
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 };
注意Line22,24,一定要从1开始。从0开始就错了,因为后面需要递推i-1,j-1。
用滚动数组法节省空间:
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 };
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 };
dp[i][j]: 在走进dungeon[i][j]之前至少应有的hp值。
一开始自己写的太罗嗦了,一堆判断语句,结果其实就用一句max(1,dp[][]-dungeon[][])就行了。
#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 };
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 };
和最大连续子数组和类似。子问题都是以当前位置作为结尾(卖出点)时的最优解。取这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 };
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
十一、
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 };
递推公式: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]. 将这两大方案取个较大值即可。
#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 };
odd是之前在奇数位达到的最大值,even是之前在偶数位达到的最大值。
follow up: 如果首尾相连呢?
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 };
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 };
如果首尾相连,其实就意味着首和尾最多只能有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 };
从下向上计算确实比从上向下要简单很多,没有额外的边界条件需要考虑。
滚动数组版:
十三、
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 };
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 };
i表示当前子串的长度(从下标0开始),j表示此时把子串分割成的左半段的长度。dp[i]就表示从0开始长度为i的子串是不是满足wordBreak的。
ref 自底向上,由小到大的构造出来。
常规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 };
改进版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 };
参考该博客,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 };
关键是对于一个特定的柱体,如何最大限度的向左右扩展。
首先看向右扩展。假设当前需要出栈的柱体的下标是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(包含),右边界不变,共有 i 个柱体。
#2: Line11 忘了加上 if(s.empty()) 这个判断。这会导致RE. Line17也忘了考虑s.empty();
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 };
先预处理再做,是一个很好的技巧。
学以致用。预处理出高度来,再利用上一题的代码。
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 };
规律:对每个小柱子X而言,它头顶上会不会有水取决于它左边所有柱子里最高的那个柱子A以及它右边所有柱子里最高的那个柱子B。A和B较矮的那个如果高过当前这个柱子X,那么这个小柱子X头顶上就能有 min(height[A],height[B]) - height[X] 这么高的水。
时间O(n),空间O(n)。
注意对n==0的边界条件的判断。【corner case !】
学习下空间复杂度为O(1)的解法。 ref1 ref2 ref3
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 };
比较左右两板,对于较短的板而言,无论另一块板(较长的板)如何向中间移动,都不可能取的比现在更大的面积。因此记录下此时的面积,将短板向中间移动。