方法一、一维DP
1 class Solution 2 { 3 public: 4 int longestValidParentheses(string s) 5 { 6 vector<int> dp(s.size(),0); 7 int res=0; 8 for(int i=s.size()-2;i>=0;i--) 9 { 10 if(s[i]==')') 11 dp[i]=0; 12 else 13 { 14 int j=i+dp[i+1]+1; 15 if(j<s.size()&&s[j]==')') 16 { 17 dp[i]+=dp[i+1]+2; 18 if(j+1<s.size()) 19 dp[i]+=dp[j+1]; 20 } 21 } 22 res=max(res,dp[i]); 23 } 24 return res; 25 } 26 };
像这种连续的子串,类似于最大连续子串和,在dp时往往是以某位置为结尾/起点。
dp[i]表示以s[i]为起点的最长连续括号数。下标j表示跨越了以s[i+1]为起点的一连串括号后到达的位置。如果该位置s[j]为'(' ,那说明以该'('为起点的有效括号数必然是0(否则就应该被包含进以s[i+1]为起点的有效括号串里了);而如果该位置s[j]为')',那正好和s[i]的'('匹配起来,因此s[i]可基于s[i+1]的基础上再加2,而且,除此之外还要再加上s[j+1] 。
ref1: Lexi's
#2: 忘的太快。很多细节: 判断 j < n 和 j + 1 < n。
方法二、栈
二、 Regular Expression Matching
DP解法:
1 class Solution 2 { 3 public: 4 bool isInterleave(string s1,string s2,string s3) 5 { 6 int m=s1.size(),n=s2.size(); 7 if(m+n!=s3.size()) return false; 8 vector<vector<int>> f(m+1,vector<int>(n+1,0)); 9 f[0][0]=true; 10 for(int i=1;i<=m;i++) 11 f[i][0]=f[i-1][0]&&(s1[i-1]==s3[i-1]); 12 for(int i=1;i<=n;i++) 13 f[0][i]=f[0][i-1]&&(s2[i-1]==s3[i-1]); 14 for(int i=1;i<=m;i++) 15 for(int j=1;j<=n;j++) 16 f[i][j]=(s1[i-1]==s3[i+j-1] && f[i-1][j])||(s2[j-1]==s3[i+j-1] && f[i][j-1]); 17 return f[m][n]; 18 } 19 };
f[i][j]表示s1从下标0开始长度为i的子串,与s2从下标0开始长度为j的子串,它们与s3从0开始长度为i+j的子串是否是interleave string。
注意这里的i,j都是长度。所以转换为下标时别忘了减1.
另外,在line8初始化时如果是以0为初始值,那就别忘了专门把f[0][0]赋值为1。
细节很多,易出错。
#2: good.
1 class Solution 2 { 3 public: 4 int maximalSquare(vector<vector<char>> &matrix) 5 { 6 if(matrix.empty()) return 0; 7 int m=matrix.size(),n=matrix[0].size(); 8 vector<vector<int>> f(m,vector<int>(n,0)); 9 int res=0; 10 for(int i=0;i<m;i++) 11 { 12 f[i][0]=matrix[i][0]-'0'; 13 res=max(res,f[i][0]); 14 } 15 for(int i=0;i<n;i++) 16 { 17 f[0][i]=matrix[0][i]-'0'; 18 res=max(res,f[0][i]); 19 } 20 for(int i=1;i<m;i++) 21 { 22 for(int j=1;j<n;j++) 23 { 24 if(matrix[i][j]=='1') 25 { 26 f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1; 27 res=max(res,f[i][j]); 28 } 29 else f[i][j]=0; 30 } 31 } 32 return res*res; 33 } 34 };
dp[i][j]表示以(i,j)为右下角的最大正方形的边长。那么,可得递推公式:dp[i][j]= min( dp[i-1][j] , dp[i][j-1] , dp[i-1][j-1] ) + 1;
该递推式可以先直观感受一下:
若该点周围3个点最小的dp值为0(即至少有一个点是‘0’),那最大正方形边长必然是1(即该点本身);
若该点周围3个点最小的dp值为1,那么至少这3个点都是‘1’,所以已经可以构成一个边长为2的正方形;
...
#2: 一堆细节易出错。题目要求的是面积,不是边长,所以最后返回的是res * res; 第一行和第一列要特殊处理,同时把它们的值都要跟res比一下并更新res。
1 class Solution 2 { 3 public: 4 int minCut(string s) 5 { 6 int n=s.size(); 7 vector<int> f(n+1,0); 8 vector<vector<int>> p(n,vector<int>(n,0)); 9 for(int i=0;i<=n;i++) 10 f[i]=n-1-i; 11 for(int i=n-1;i>=0;i--) 12 { 13 for(int j=i;j<n;j++) 14 { 15 if(s[i]==s[j]&&(j-i<2||p[i+1][j-1])) 16 { 17 p[i][j]=1; 18 f[i]=min(f[i],f[j+1]+1); 19 } 20 } 21 } 22 return f[0]; 23 } 24 };
设len=s.size(), f[i]表示从s[i]开始一直到s[len-1]这段子串的minCut数(显然,最大值即为这段子串包含的字符数减1)。
这里之所以有除了f[0]~f[len-1]之外还有个f[len]= -1,是因为后面会用到。
p[i][j]表示s[i...j]这段子串是否是回文串。判断依据就是 if ( s[i]==s[j] && (j-i<2 || p[i+1][j-1]) ) ,这个如果成立则说明s[i...j]是个回文串,那么就可以尝试更新一下 f[i]=min(f[i] , f[j+1]+1) ,即如果在j和j+1之间cut一下,那么s[i...len-1]即分成了s[i...j]和s[j+1...len-1],此时的cut数等于f[j+1]+1.
ref: soul, spring
#2: 太容易忘。
O(n) space: 【非最优】
1 class Solution 2 { 3 public: 4 int maxProduct(vector<int> &nums) 5 { 6 int n=nums.size(); 7 vector<int> dp_max(n,0); 8 vector<int> dp_min(n,0); 9 int res=dp_max[0]=dp_min[0]=nums[0]; 10 for(int i=1;i<n;i++) 11 { 12 dp_max[i]=max(nums[i],max(dp_max[i-1]*nums[i],dp_min[i-1]*nums[i])); 13 dp_min[i]=min(nums[i],min(dp_max[i-1]*nums[i],dp_min[i-1]*nums[i])); 14 res=max(res,dp_max[i]); 15 } 16 return res; 17 } 18 };
O(1) space 【最优】
1 class Solution 2 { 3 public: 4 int maxProduct(vector<int> &nums) 5 { 6 int n=nums.size(); 7 int maxsofar,minsofar,maxpre,minpre; 8 int res=maxsofar=minsofar=maxpre=minpre=nums[0]; 9 for(int i=1;i<n;i++) 10 { 11 maxsofar=max(nums[i],max(maxpre*nums[i],minpre*nums[i])); 12 minsofar=min(nums[i],min(maxpre*nums[i],minpre*nums[i])); 13 res=max(res,maxsofar); 14 maxpre=maxsofar; 15 minpre=minsofar; 16 } 17 return res; 18 } 19 };
#2: 细节容易出错。
七、 Decode Ways
1 class Solution 2 { 3 public: 4 int numDecodings(string s) 5 { 6 int n=s.size(); 7 if(n==0) return 0; 8 vector<int> dp(n+1,0); 9 dp[0]=1; 10 if(s[0]!='0') dp[1]=1; 11 for(int i=2;i<=n;i++) 12 { 13 if(s[i-1]!='0') 14 dp[i]+=dp[i-1]; 15 if(s[i-2]=='1'||(s[i-2]=='2' && s[i-1]<='6')) 16 dp[i]+=dp[i-2]; 17 } 18 return dp[n]; 19 } 20 };
dp[i]里的i表示长度,即从下标0开始的长度为i的子串的decode ways数。
要特别注意,dp[0]值为1。这是为了递推后面的值的需要。
与climb stairs是类似的。 可以用滚动数组优化空间复杂度。