leetcode Ch2-Dynamic Programming I
一、
class Solution
{
public:
int minDistance(string t1,string t2)
{
int len1=t1.size(),len2=t2.size();
vector<vector<int>> dp(len1+,vector<int>(len2+,));
for(int i=;i<=len1;i++) dp[i][]=i;
for(int i=;i<=len2;i++) dp[][i]=i;
for(int i=;i<=len1;i++)
{
for(int j=;j<=len2;j++)
{
dp[i][j]=min(min(dp[i-][j]+,dp[i][j-]+),dp[i-][j-]+(t1[i-]==t2[j-]?:));
}
}
return dp[len1][len2];
}
};
需要注意的细节是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。
class Solution
{
public:
int numDistinct(string s,string t)
{
int sLen=s.size(),tLen=t.size();
vector<vector<int>> dp(sLen+,vector<int>(tLen+,));
for(int i=;i<=sLen;i++) dp[i][]=;
for(int i=;i<=sLen;i++)
{
for(int j=;j<=tLen;j++)
{
dp[i][j]=dp[i-][j]+(s[i-]==t[j-]? dp[i-][j-]: );
}
}
return dp[sLen][tLen];
}
};
递推式不难,关键是正确理解题意和初始化。
题目说的是T是S任意删掉若干字符得到的结果。那么当T为空时,从S中只有一种删法能够得到T(那就是删光)。所以dp[i][0]都是1. 这一步初始化很重要。
dp[i][j]是S长度为i(从下标0开始),T长度为j(从下标0开始)所对应的删法的个数。
易出错。
#2: 忘记递推关系了; 初始化的时候是=1;
用vector,不用数组 (推荐)
class Solution {
public:
bool isScramble(string s1, string s2) {
int N = s1.size();
if (N != s2.size()) {
return false;
}
vector<vector<vector<bool>>> f(N + , vector<vector<bool>>(N, vector<bool>(N, false)));
f[][][] = true;
for (int i = ; i < N; i++) {
for (int j = ; j < N; j++) {
f[][i][j] = (s1[i] == s2[j]);
}
}
for (int n = ; n <= N; n++) {
for (int i = ; i + n <= N; i++) {
for (int j = ; j + n <= N; j++) {
for (int k = ; k < n; k++) {
if ((f[k][i][j] && f[n - k][i + k][j + k]) || (f[k][i][j + n - k] && f[n - k][i + k][j])) {
f[n][i][j] = true;
break;
}
}
}
}
}
return f[N][][];
}
};
用数组(不推荐)
class Solution
{
public:
bool isScramble(string s1,string s2)
{
const int N=s1.size();
if(N!=s2.size()) return false;
bool f[N+][N][N];
fill_n(&f[][][],(N+)*N*N,false);
for(int i=;i<N;i++)
for(int j=;j<N;j++)
f[][i][j]=(s1[i]==s2[j]);
for(int n=;n<=N;n++)
{
for(int i=;i<=N-n;i++)
{
for(int j=;j<=N-n;j++)
{
for(int k=;k<n;k++)
{
if((f[k][i][j]&&f[n-k][i+k][j+k])||(f[k][i][j+n-k]&&f[n-k][i+k][j]))
{
f[n][i][j]=true;
break;
}
}
}
}
}
return f[N][][];
}
};
这道题很多细节容易出错,要谨慎。
注意:这个代码在VS下通不过,虽然能过OJ。
#2: 忘记对 f[1][i][j] 的初始化了。而且写的巨慢。
注意:k是从1到n-1的,不能从0开始,那会导致越界。
二、最长递增子序列 LIS
#2: 老是重复犯一个错误!!!每次应用binarySearch的时候都用到了nums上,应该是用到存储当前位置最小元素的数组B上!!!
三、矩阵链乘
int matrixChain(vector<pair<int,int>> &vp)
{
int len = vp.size();
vector<vector<int>> dp(SIZE, vector<int>(SIZE, INT_MAX));
for (int i = ; i<len; i++) dp[i][i] = ;
for (int L = ; L <= len; L++)
{
for (int i = ; i <= len - L; i++)
{
int j = i + L - ;
int minV = INT_MAX;
for (int k = i; k < j; k++)
{
if (dp[i][k] + dp[k + ][j] + vp[i].first*vp[k].second*vp[j].second<minV)
minV = dp[i][k] + dp[k + ][j] + vp[i].first*vp[k].second*vp[j].second;
}
dp[i][j] = minV;
}
}
return dp[][len - ];
}
需要注意的是Line12,k<j,不能是k<=j. 因为我们的划分是划分成[i...k][k+1...j],所以对于后半段需要满足k+1<=j,即k必须小于j。
完整代码:
不能首尾相连版:
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std; #define SIZE 1000
void init(vector<pair<int, int>> &vp)
{
int n;
cin >> n;
while (n--)
{
int x, y;
cin >> x >> y;
vp.push_back(make_pair(x, y));
}
} int matrixChain(vector<pair<int,int>> &vp)
{
int len = vp.size();
vector<vector<int>> dp(SIZE, vector<int>(SIZE, INT_MAX));
for (int i = ; i<len; i++) dp[i][i] = ;
for (int L = ; L <= len; L++)
{
for (int i = ; i <= len - L; i++)
{
int j = i + L - ;
int minV = INT_MAX;
for (int k = i; k < j; k++)
{
if (dp[i][k] + dp[k + ][j] + vp[i].first*vp[k].second*vp[j].second<minV)
minV = dp[i][k] + dp[k + ][j] + vp[i].first*vp[k].second*vp[j].second;
}
dp[i][j] = minV;
}
}
return dp[][len - ];
} int main()
{
vector<pair<int, int>> vp;
init(vp);
cout << matrixChain(vp) << endl;
}
可以首尾相连版:
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std; struct mat {
int rowNum;
int colNum;
}; void init(vector<mat> &v) {
int n;
cin >> n;
while (n--) {
mat tmp;
cin >> tmp.rowNum >> tmp.colNum;
v.push_back(tmp);
}
} int matMultiply(vector<mat> &v) {
int len = v.size();
if (len == ) {
return ;
}
vector<vector<int> > dp( * len, vector<int>( * len, INT_MAX));
for (int i = ; i < * len; i++) {
dp[i][i] = ;
}
int minV = INT_MAX;
for (int L = ; L <= len; L++) {
for (int i = ; i <= * len - L; i++) {
int j = i + L - ;
for (int k = i; k < j; k++) {
int tmp = dp[i][k] + dp[k + ][j] + v[i % len].rowNum * v[k % len].colNum * v[j % len].colNum;
dp[i][j] = min(dp[i][j], tmp);
}
if (L == len) {
minV = min(minV, dp[i][j]);
}
}
}
return minV;
} int main() {
vector<mat> v;
init(v);
cout << matMultiply(v) << endl;
return ;
}
#2: 犯的错误:if(L == len) 放错位置,放在了i循环外面。
四、最长公共子序列 LCS
#include <iostream>
#include <vector>
#include <string>
using namespace std; int lcs(string t1, string t2, vector<vector<int>> &trace)
{
int len1 = t1.size(), len2 = t2.size();
vector<vector<int>> dp(t1.size() + , vector<int>(t2.size() + , ));
for (int i = ; i <= len1; i++)
{
for (int j = ; j <= len2; j++)
{
if (t1[i - ] == t2[j - ])
{
dp[i][j] = dp[i - ][j - ] + ;
trace[i][j] = ;
}
else
{
if (dp[i - ][j]>dp[i][j - ])
{
dp[i][j] = dp[i - ][j];
trace[i][j] = ;
}
else
{
dp[i][j] = dp[i][j - ];
trace[i][j] = ;
}
}
}
}
return dp[len1][len2];
} void print(vector<vector<int>> &trace, int i, int j, string t1)
{
if (i == || j == ) return;
if (trace[i][j] == )
{
print(trace, i - , j - ,t1);
cout << t1[i-];
}
else if (trace[i][j] == )
print(trace, i - , j, t1);
else if(trace[i][j]==)
print(trace, i, j - , t1);
} int main()
{
string t1;
string t2;
cin >> t1 >> t2;
vector<vector<int>> trace(t1.size()+, vector<int>(t2.size()+, -));
lcs(t1, t2, trace);
print(trace, t1.size(), t2.size(), t1);
//cout << endl;
}
这里仍然是需要注意下标问题。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)
referenceand 算法概论,算法导论
#2: 忘记了怎么递归地output。
五、
1. 最长重复子串 Longest Repeat Substring (LRS)
最长重复子串是单字符串问题。本题中默认为允许重叠。
直观做法 O(n^3)
#include <string>
#include <vector>
#include <iostream>
using namespace std; int comp(string str, int i, int j)
{
int len = ;
while (i<str.size() && j<str.size() && str[i] == str[j])
{
len++;
i++; j++;
}
return len;
}
int lrs(string str,int &maxIndex)
{
int maxV = ;
for (int i = ; i<str.size(); i++)
{
for (int j = i + ; j<str.size(); j++)
{
int len = comp(str, i, j);
if (len>maxV) {
maxV = len;
maxIndex = i;
}
}
}
return maxV;
} void output(string str, int maxIndex,int len)
{
while (len--)
{
cout << str[maxIndex++];
}
} int main()
{
string str("banana");
int maxIndex = ;
int len= lrs(str,maxIndex);
output(str,maxIndex,len);
return ;
}
后缀数组做法:
最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和
2. Longest Substring Without Repeating Characters [最长不重复子串]
class Solution {
public:
int lengthOfLongestSubstring(string str) {
vector<bool> exist(, false);
int n = str.size();
int i = , j = ;
int maxlen = ;
while (j < n) {
if (exist[str[j]]) {
maxlen = max(maxlen, j - i);
while (str[i] != str[j]) {
exist[str[i]] = false;
i++;
}
i++;
j++;
} else {
exist[str[j]] = true;
j++;
}
}
maxlen = max(maxlen, n - i);
return maxlen;
}
};
六、
class Solution
{
public:
int numTrees(int n)
{
vector<int> g(n+,);
g[]=g[]=;
for(int i=;i<=n;i++)
{
for(int j=;j<i;j++)
{
g[i]+=g[j]*g[i--j];
}
}
return g[n];
}
};
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
class Solution
{
public:
vector<TreeNode*> generateTrees(int n)
{
return dfs(,n);
}
vector<TreeNode*> dfs(int min,int max)
{
vector<TreeNode*> res;
if(min>max)
{
res.push_back(NULL);//表示空树,不可缺少。否则会影响下面对leftSub,rightSub的循环。
return res;
}
for(int i=min;i<=max;i++)
{
vector<TreeNode*> leftSub=dfs(min,i-);
vector<TreeNode*> rightSub=dfs(i+,max);
for(int j=;j<leftSub.size();j++)
{
for(int k=;k<rightSub.size();k++)
{
TreeNode* root=new TreeNode(i);
root->left=leftSub[j];
root->right=rightSub[k];
res.push_back(root);
}
}
}
return res;
}
};
对于Line11-15,当min>max时表示空树。空树必须要加进去一个NULL来表示出来,否则,对于下面分别从leftSub和rightSub取出一个元素组成两个子树时,如果一边(假设为leftSub)为空且没有表示出空树来的话,这一层for循环就不能执行了,那对于有n-1个节点的rightSub的各种形态都没法执行循环列出来了。整个双层for循环都没法用了。
#2: 想不出怎么实现的了。而且Line24如果放在两层循环的外面是会WA的,还没明白为什么。
七、
1. Maximum Subarray 最大子数组和
code1: [美观]
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
int dp=,res=INT_MIN;
for(int i=;i<nums.size();i++)
{
dp=max(nums[i],dp+nums[i]);
res=max(res,dp);
}
return res;
}
};
code2:[不够美观]
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
int sum=;
int maxV=INT_MIN;
for(int i=;i<nums.size();i++)
{
if(sum<) sum=;
sum+=nums[i];
if(sum>maxV) maxV=sum;
}
return maxV;
}
};
采用dp来做的话,dp[n]代表从arr[0]到arr[n]的最大子数组和。它的子问题dp[n-1]是从arr[0]到arr[n-1]的最大子数组和。注意,起点始终是arr[0].
看一下july列出的扩展问题。以及勇幸的整理。
- 如果数组是二维数组,同样要你求最大子数组的和列?
- 如果是要你求子数组的最大乘积列?
- 如果同时要求输出子段的开始和结束列?
另外,关于如何用divide and conquer来解此问题,详见此贴。
2. 环形最大连续数组和
int maxConsSum(const vector<int> &arr)
{
int dp=,res=;
for(int i=;i<arr.size();i++)
{
dp=max(arr[i],dp+arr[i]);
res=max(res,dp);
}
return res;
} int minConsSum(const vector<int> &arr)
{
int dp=,res=;
for(int i=;i<arr.size();i++)
{
dp=min(arr[i],dp+arr[i]);
res=min(dp,res);
}
return res;
} int maxConsSum2(const vector<int> &arr) {
int sum=;
for(int i=;i<arr.size();i++)
sum+=arr[i];
int m1=maxConsSum(arr);
int m2=minConsSum(arr);
int res=max(m1,sum-m2);
return res;
}
一开始的思路和ref2里刚开始说的那样,想着首尾拼接起来成一个长数组。但是貌似需要O(n*n)才行(难道是列举出长度为arr.size()的这n个数组,分别对其执行最大子段和MaxSubArray的O(n)算法?还没进行实现。)后来参考了ref2和ref1的代码,如果跨越了末尾就用(数组元素之和-最小子段和),如果没跨越就用(最大子段和)。对其取max即可。
注意一个与上题不同的细节。这题里说可以允许有空段,而上题里要求是至少含有一个元素。因此res的初值一个为INT_MIN,一个为0.
另外,再看一下ref3提到的几个拓展问题。
可以理解为:若环形最大子段和需要跨越首尾,那么最小子段和必然不需要跨越。举个极端的例子就是,首尾元素都是正数,其余元素都是负数。
空间复杂度O(n):【非最优】
class Solution
{
public:
int climbStairs(int n)
{
vector<int> dp(n+,);
dp[]=dp[]=;
for(int i=;i<=n;i++)
{
dp[i]=dp[i-]+dp[i-];
}
return dp[n];
}
};
若不能开辟数组的话。
空间复杂度O(1):
class Solution
{
public:
int climbStairs(int n)
{
int prev=,curr=;
int tmp=;
for(int i=;i<=n;i++)
{
tmp=curr;
curr+=prev;
prev=tmp;
}
return curr;
}
};
使用3个变量即可。和Fibonacci一样。
九、
1. Unique Paths
空间复杂度O(mn) 【非最优】
class Solution
{
public:
int uniquePaths(int m,int n)
{
vector<vector<int>> dp(m,vector<int>(n,));
for(int i=;i<m;i++)
dp[i][]=;
for(int i=;i<n;i++)
dp[][i]=;
for(int i=;i<m;i++)
{
for(int j=;j<n;j++)
{
dp[i][j]=dp[i-][j]+dp[i][j-];
}
}
return dp[m-][n-];
}
};
空间复杂度O(n) 【非最优】
class Solution
{
public:
int uniquePaths(int m,int n)
{
vector<int> dp(n,);
for(int i=;i<m;i++)
{
for(int j=;j<n;j++)
{
dp[j]=dp[j]+dp[j-];
}
}
return dp[n-];
}
};
等式左边的dp[j]代表dp[i][j],等式右边的dp[j]代表dp[i-1][j]. 而dp[j-1]代表dp[i][j-1].
利用公式 【最优】
class Solution
{
public:
int uniquePaths(int m,int n)
{
int N=m+n-;
int k=min(n-,m-);
double res=1.0;
for(int i=;i<=k;i++)
res*=(double)(N-i+)/i;
return round(res);
}
};
注意: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。
class Solution
{
public:
int uniquePathsWithObstacles(vector<vector<int>> &obstacleGrid)
{
if(obstacleGrid.empty()) return ;
int m=obstacleGrid.size();
int n=obstacleGrid[].size();
vector<vector<int>> dp(m,vector<int>(n,));
for(int i=;i<m;i++)
{
if(obstacleGrid[i][]==)
break;
dp[i][]=;
}
for(int i=;i<n;i++)
{
if(obstacleGrid[][i]==)
break;
dp[][i]=;
}
for(int i=;i<m;i++)
{
for(int j=;j<n;j++)
{
if(obstacleGrid[i][j]==)
dp[i][j]=;
else
dp[i][j]=dp[i-][j]+dp[i][j-];
}
}
return dp[m-][n-];
}
};
注意Line22,24,一定要从1开始。从0开始就错了,因为后面需要递推i-1,j-1。
用滚动数组法节省空间:
class Solution
{
public:
int minPathSum(vector<vector<int>> &grid)
{
if(grid.empty()) return ;
int m=grid.size(),n=grid[].size();
vector<vector<int>> dp(m,vector<int>(n,));
dp[][]=grid[][];
for(int i=;i<m;i++)
dp[i][]=dp[i-][]+grid[i][];
for(int i=;i<n;i++)
dp[][i]=dp[][i-]+grid[][i];
for(int i=;i<m;i++)
{
for(int j=;j<n;j++)
{
dp[i][j]=min(dp[i-][j],dp[i][j-])+grid[i][j];
}
}
return dp[m-][n-];
}
};
4. Dungeon Game
class Solution
{
public:
int calculateMinimumHP(vector<vector<int>> &dungeon)
{
if(dungeon.empty()) return ;
int m=dungeon.size(),n=dungeon[].size();
vector<vector<int>> dp(m,vector<int>(n,));
dp[m-][n-]=max(,-dungeon[m-][n-]);
for(int i=m-;i>=;i--)
dp[i][n-]=max(,dp[i+][n-]-dungeon[i][n-]);
for(int i=n-;i>=;i--)
dp[m-][i]=max(,dp[m-][i+]-dungeon[m-][i]);
for(int i=m-;i>=;i--)
for(int j=n-;j>=;j--)
dp[i][j]=max(,min(dp[i+][j],dp[i][j+])-dungeon[i][j]);
return dp[][];
}
};
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:
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
if(prices.size()<) return ;
int dp=,minV=prices[];
for(int i=;i<prices.size();i++)
{
dp=max(dp,prices[i]-minV);
minV=min(minV,prices[i]);
}
return dp;
}
};
code2:
class Solution
{
public:
int maxProfit(vector<int> &prices)
{
if(prices.size()==) return ;
int min=prices[];
int res=;
for(int i=;i<prices.size();i++)
{
if(prices[i]<min) min=prices[i];
if(prices[i]-min>res) res=prices[i]-min;
}
return res;
}
};
和最大连续子数组和类似。子问题都是以当前位置作为结尾(卖出点)时的最优解。取这N个最优解中的最大值。但是MaxSubArray里是一定包括当前位置的,本题里不一定包括当前位置,只是指定一个范围。
2. Best Time to Buy and Sell Stock III
class Solution
{
public:
int maxProfit(vector<int> &prices)
{
if(prices.size()<) return ;
const int n=prices.size();
vector<int> f(n,);
vector<int> g(n,);
int minV=prices[],maxV=prices[n-];
for(int i=;i<n;i++)
{
f[i]=max(f[i-],prices[i]-minV);
minV=min(minV,prices[i]);
}
for(int i=n-;i>=;i--)
{
g[i]=max(g[i+],maxV-prices[i]);
maxV=max(maxV,prices[i]);
}
int res=;
for(int i=;i<n;i++)
{
res=max(f[i]+g[i],res);
}
return res;
}
};
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
class Solution
{
public:
int rob(vector<int> &nums)
{
const int n=nums.size();
if(n==) return ;
vector<int> dp(n+,);
dp[]=;
dp[]=nums[];
for(int i=;i<=n;i++)
{
dp[i]=max(dp[i-],dp[i-]+nums[i-]);
}
return dp[n];
}
};
递推公式: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
class Solution
{
public:
int rob(vector<int> &nums)
{
if(nums.size()==) return ;
int odd=,even=;
for(int i=;i<nums.size();i++)
{
if(i%==)
even=max(odd,even+nums[i]);
else
odd=max(even,odd+nums[i]);
}
return even>odd?even:odd;
}
};
odd是之前在奇数位达到的最大值,even是之前在偶数位达到的最大值。
follow up: 如果首尾相连呢?
code 1:
class Solution
{
public:
int rob(vector<int> &nums)
{
if(nums.size()==) return ;
if(nums.size()==) return nums[];
int odd=,even=;
for(int i=;i<nums.size()-;i++)
{
if(i%==)
even=max(odd,even+nums[i]);
else
odd=max(even,odd+nums[i]);
}
int res1=max(even,odd);
even=odd=;
for(int i=;i<nums.size();i++)
{
if(i%==)
even=max(odd,even+nums[i]);
else
odd=max(even,odd+nums[i]);
}
int res2=max(even,odd);
return max(res1,res2);
}
};
code 2:
class Solution {
public:
int rob(vector<int> &nums) {
int n = nums.size();
if (n < ) {
return ;
}
if (n == ) {
return nums[];
}
vector<int> dp(n + , );
int res1 = , res2 = ;
dp[] = nums[];
for (int i = ; i < n; i++) {
dp[i] = max(dp[i - ], dp[i - ] + nums[i - ]);
}
res1 = dp[n - ];
fill_n(dp.begin(), dp.size(), );
dp[] = nums[];
for (int i = ; i < n; i++) {
dp[i] = max(dp[i - ], dp[i - ] + nums[i]);
}
res2 = dp[n - ];
return max(res1, res2);
}
};
如果首尾相连,其实就意味着首和尾最多只能有1个(也可能都没有,反正不能同时出现)。所以就对上一题的算法跑两遍,一遍去掉头,一遍去掉尾。取这两次结果的较大值即可。
十二、 Triangle
class Solution
{
public:
int minimumTotal(vector<vector<int>> &triangle)
{
int n=triangle.size();
vector<vector<int>> dp(n,vector<int>(n,));
for(int i=;i<=n-;i++)
dp[n-][i]=triangle[n-][i];
for(int i=n-;i>=;i--)
{
for(int j=;j<=i;j++)
{
dp[i][j]=min(dp[i+][j],dp[i+][j+])+triangle[i][j];
}
}
return dp[][];
}
};
从下向上计算确实比从上向下要简单很多,没有额外的边界条件需要考虑。
滚动数组版:
十三、
1. Word Break
先来个dfs版的。一阵子不练dfs就生疏了。
class Solution
{
public:
bool wordBreak(string s, unordered_set<string> &wordDict)
{
return dfs(s, wordDict, );
}
bool dfs(string s, unordered_set<string> &wordDict, int start)
{
if (start == s.size())
return true;
for (int i = start; i<s.size(); i++)
{
if (wordDict.count(s.substr(start, i - start + ))>)
{
if (dfs(s, wordDict, i + ))
return true;
}
}
return false;
}
};
DP:
class Solution
{
public:
bool wordBreak(string s, unordered_set<string> &wordDict)
{
vector<bool> dp(s.size()+,);
dp[]=;
for(int i=;i<=s.size();i++)
{
for(int j=;j<i;j++)
{
if(dp[j]&&wordDict.count(s.substr(j,i-j))>)
{
dp[i]=true;
break;
}
}
}
return dp[s.size()];
}
};
i表示当前子串的长度(从下标0开始),j表示此时把子串分割成的左半段的长度。dp[i]就表示从0开始长度为i的子串是不是满足wordBreak的。
ref 自底向上,由小到大的构造出来。
常规dfs版:【TLE】
class Solution
{
public:
vector<string> wordBreak(string s,unordered_set<string> &wordDict)
{
dfs(s,wordDict,);
return result;
}
vector<string> result;
string path;
void dfs(string s,unordered_set<string> &wordDict,int start)
{
if(start==s.size())
{
result.push_back(path);
return;
}
for(int i=start;i<s.size();i++)
{
if(wordDict.count(s.substr(start,i-start+))>)
{
if(!path.empty()) path+=" ";
path+=s.substr(start,i-start+);
dfs(s,wordDict,i+);
path.resize(path.size()-(i-start+));
if(!path.empty()) path.resize(path.size()-);
}
}
}
};
改进版dfs--dp剪枝版:(除了利用word break里的函数外,只比上面的 [常规dfs版] 多了line6这一句话)
class Solution
{
public:
vector<string> wordBreak(string s,unordered_set<string> &wordDict)
{
if(!isWordBreak(s,wordDict)) return result;
dfs(s,wordDict,);
return result;
}
private:
vector<string> result;
string path;
void dfs(string s,unordered_set<string> &wordDict,int start)
{
if(start==s.size())
{
result.push_back(path);
return;
}
for(int i=start;i<s.size();i++)
{
if(wordDict.count(s.substr(start,i-start+))>)
{
if(!path.empty()) path+=" ";
path+=s.substr(start,i-start+);
dfs(s,wordDict,i+);
path.resize(path.size()-(i-start+));
if(!path.empty()) path.resize(path.size()-);
}
}
} bool isWordBreak(string s, unordered_set<string> &wordDict)
{
vector<bool> dp(s.size()+,);
dp[]=;
for(int i=;i<=s.size();i++)
{
for(int j=;j<i;j++)
{
if(dp[j]&&wordDict.count(s.substr(j,i-j))>)
{
dp[i]=true;
break;
}
}
}
return dp[s.size()];
}
};
参考该博客,dp的作用只有1行,line6处。
纯dp版:
refer to discuss
十四、
1. Largest Rectangle in Histogram
class Solution
{
public:
int largestRectangleArea(vector<int> &height)
{
stack<int> s;
height.push_back();
int i=,res=;
while(i<height.size())
{
if(s.empty()||height[i]>=height[s.top()])
s.push(i++);
else
{
int tmp=s.top();
s.pop();
res=max(res,height[tmp]*(s.empty()?i:i-s.top()-));
}
}
return res;
}
};
关键是对于一个特定的柱体,如何最大限度的向左右扩展。
首先看向右扩展。假设当前需要出栈的柱体的下标是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();
class Solution
{
public:
int maximalRectangle(vector<vector<char>> &matrix)
{
if(matrix.empty()) return ;
int m=matrix.size(),n=matrix[].size();
vector<int> height(n,);
int res=;
for(int i=;i<m;i++)
{
for(int j=;j<n;j++)
{
if(matrix[i][j]=='')
height[j]=;
else
height[j]+=;
}
res=max(res,largestRectangleArea(height));
}
return res;
} int largestRectangleArea(vector<int> &height)
{
stack<int> s;
height.push_back();
int i=,res=;
while(i<height.size())
{
if(s.empty()||height[i]>=height[s.top()])
s.push(i++);
else
{
int tmp=s.top();
s.pop();
res=max(res,height[tmp]*(s.empty()?i:i-s.top()-));
}
}
return res;
}
};
先预处理再做,是一个很好的技巧。
学以致用。预处理出高度来,再利用上一题的代码。
class Solution
{
public:
int trap(vector<int> &height)
{
int n=height.size();
if(n==) return ;
vector<int> leftMost(n,);
vector<int> rightMost(n,);
int maxV=,res=;
for(int i=;i<n;i++)
{
leftMost[i]=maxV;
maxV=max(maxV,height[i]);
}
maxV=height[n-];
for(int i=n-;i>=;i--)
{
rightMost[i]=maxV;
maxV=max(maxV,height[i]);
}
for(int i=;i<n;i++)
{
if(min(leftMost[i],rightMost[i])>height[i])
res+=min(leftMost[i],rightMost[i])-height[i];
}
return res;
}
};
规律:对每个小柱子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
class Solution
{
public:
int maxArea(vector<int> &height)
{
if(height.empty()) return ;
int p1=,p2=height.size()-;
int res=;
while(p1<=p2)
{
if(height[p1]<height[p2])
{
res=max(res,height[p1]*(p2-p1));
p1++;
}
else
{
res=max(res,height[p2]*(p2-p1));
p2--;
}
}
return res;
}
};
比较左右两板,对于较短的板而言,无论另一块板(较长的板)如何向中间移动,都不可能取的比现在更大的面积。因此记录下此时的面积,将短板向中间移动。
leetcode Ch2-Dynamic Programming I的更多相关文章
- [LeetCode] 139. Word Break_ Medium tag: Dynamic Programming
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine ...
- [LeetCode] 45. Jump Game II_ Hard tag: Dynamic Programming
Given an array of non-negative integers, you are initially positioned at the first index of the arra ...
- [LeetCode] 55. Jump Game_ Medium tag: Dynamic Programming
Given an array of non-negative integers, you are initially positioned at the first index of the arra ...
- [LeetCode] 63. Unique Paths II_ Medium tag: Dynamic Programming
A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). The ...
- [LeetCode] 121. Best Time to Buy and Sell Stock_Easy tag: Dynamic Programming
Say you have an array for which the ith element is the price of a given stock on day i. If you were ...
- [LeetCode] 53. Maximum Subarray_Easy tag: Dynamic Programming
Given an integer array nums, find the contiguous subarray (containing at least one number) which has ...
- [LeetCode] 312. Burst Balloons_hard tag: 区间Dynamic Programming
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by ...
- [LeetCode] 64. Minimum Path Sum_Medium tag: Dynamic Programming
Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which ...
- [LeetCode] 72. Edit Distance_hard tag: Dynamic Programming
Given two words word1 and word2, find the minimum number of operations required to convert word1to w ...
- [LeetCode] 152. Maximum Product Subarray_Medium tag: Dynamic Programming
Given an integer array nums, find the contiguous subarray within an array (containing at least one n ...
随机推荐
- Jmeter之测试报告
当我们完成测试后,需要通过报告来查看测试结果 一.聚合报告 Label:每个JMeter的element的Name值.例如HTTP Request的Name #Samples:发出请求数量.例如:如第 ...
- Spark 概念学习系列之Spark Core(十五)
不多说,直接上干货! 最关键的是转换算子Transformations和缓存算子Actions. 主要是对RDD进行操作. RDD Objects -> Scheduler(DAGSched ...
- js--常量,变量
常量 内存中的一个的固定的地址,其中的数值也是固定的 变量 内存的一个地址,其中的内容有我们更改维护 值类型与引用类型 改变值类型的变量时,影响值的变量 全大写的名称一般为常量 var a = 1 v ...
- C++中文件流操作
一.C++中流和流操作符 C++中把数据之间的传输操作称为流,流既可以表示数据从内存传送到某个载体或设备中,即输出流,也可以表示数据从某个载体或设备传送到内存缓冲区变量中,即输入流.C++输入输出除了 ...
- 图像RGB2YUV与YUV2RGB格式互转介绍
1 YUV格式与RGB格式说明 由于不同国家的电视信号系统支持的图像格式不同,有YUV格式成像,也有RGB格式成像,因此为了保证兼容性,需要进行RGB与YUV格式的互转. 另外YUV格式具有亮度信息和 ...
- 《Think Python》第6章学习笔记
目录 6.1 返回函数值(Return values) 6.2 增量式开发(Incremental development) 6.3 组合(Composition) 6.4 布尔函数(Boolean ...
- tomcat主页打不开,tomcat manager 配置,Failed to start component [StandardEngine[Catalina].
Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/Serv]] ...
- Tomcat源码分析——Session管理分析(下)
前言 在<TOMCAT源码分析——SESSION管理分析(上)>一文中我介绍了Session.Session管理器,还以StandardManager为例介绍了Session管理器的初始化 ...
- SQL Serever学习5——数据库配置
数据库的主要属性 限制访问 用来设置数据允许用户访问的状态,或者说允许多少客户访问,有3个选项: MULTI_USER(多个),大多数数据库正常状态,允许多个用户同时访问该数据库. SINGLE_US ...
- [javaSE] 网络编程(URLConnection)
获取URL对象,new出来,构造参数:String的路径 调用URL对象的openConnection()方法,获取URLConnection对象 调用URLConnection对象的getInput ...