1.乘积连续最大子数组
这道题的关键是转换思想,将maxF[i]看作表示以第 i 个元素结尾的乘积最大子数组的乘积,下次每次要求新的maxF[i+1]时可以去前面的maxF数组里面找当前最大值是什么。题目要求连续乘积最大,好就好在连续上,因为要求连续,我们就可以只看上一个的值了,无非是上一个最大值乘以当前值会不会大于当前值,在二者之中取最大即可。有的题不要求连续,比如后面的最长递增子序列,最长公共子序列这道题,那就要从头看一遍我们的动规数组了找出所有存储的之前状态里符合我们要求的值,这就是两层for循环。这也是为什么有的动态规划是一层for循环,有的动态规划题是两层for循环的解释。这种解释应该是比较好理解的那一类了。
如果这道题说明数组nums全为正数的话,应该是一道简单题。但是因为数组里有负数,负负得正的情况必须考虑,所以同时更新一个minF,第一次写确实想不到这种解法,甚至看都看不太明白。minF不多做解释了,多看几遍,多写几遍,写这些破题次数多了时间长了之后,能有一种好像就是本该如此的想法时,觉得正常人就是这该是这么想的时候,对动态规划这种思想就可以说基本掌握了。
此题有一个注意点是maxF[0]=nums[0];minF[0]=nums[0];初始值要置为nums[0]
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
vector<int> maxF(n), minF(n);
maxF[0]=nums[0];
minF[0]=nums[0];
for (int i = 1; i < n; ++i) {
maxF[i] = max(nums[i], max(maxF[i - 1] * nums[i], minF[i - 1] * nums[i]));
minF[i] = min(nums[i], min(maxF[i - 1] * nums[i], minF[i - 1] * nums[i]));
}
return *max_element(maxF.begin(), maxF.end());
}
};
2.最长递增子序列
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列
这道题是不要求连续,就有点麻烦了,应该知道起码要两层for才能解决了.
值得注意的是第一层for循环每次要把dp[i]置1,然后第二层for循环有一个逆向的想法,以i为标准卡住,遍历的是从j到i的所有元素。在第二层循环里dp[i] = max(dp[i], dp[j] + 1)这句话代码里i是不动的,dp[i]是变化的。有一点从后往前看的意思,不是一般逻辑上for循环从前往后遍历就行了。这破题是真烦,第一次写很难写的出来。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = (int)nums.size();
if (n == 0) {
return 0;
}
vector<int> dp(n, 0);
for (int i = 0; i < n; ++i) {
dp[i] = 1;
for (int j = 0; j < i; ++j)
{
if (nums[j] < nums[i])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
return *max_element(dp.begin(), dp.end());
}
};
第三题更是重量级,脑子没转过来的话很难理清思路。最长公共子序列
给定两个字符串 text1
和 text2
,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0
。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
- 例如,
"ace"
是"abcde"
的子序列,但"aec"
不是"abcde"
的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
所有动态规划的关键是dp数组的意义,贯穿整题思路都是这一件事,这题也一样,dp数组的意义是,长度从0到i-1的text1和长度为从0到j-1的text2的最长公共子序列长度为dp[i][j]
int longestCommonSubsequence(string text1, string text2) {
vector<vector<int>>dp(text1.size()+1,vector<int>(text2.size()+1,0));
//dp数组的意义是,长度从0到i-1的text1和长度为从0到j-1的text2的最长公共子序列长度为dp[i][j]
for(int i=1;i<=text1.size();i++)
{
for(int j=1;j<=text2.size();j++)
{
if(text1[i-1]==text2[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[text1.size()][text2.size()];
}
当text1[i-1]==text2[j-1]相等时,很容易理解直接让dp[i-1][j-1]加1就行了,但是为什么当ext1[i-1]!=text2[j-1]不相等时,dp数组的状态转移是取max(dp[i-1][j],dp[i][j-1])的最大值。这一点是当初卡住我思路的一个点。下图画的就很好,很直观
当最新的字符不相等的时候,比如f和e,直观的思路是:既然已经确定它们不相等了,肯定是退回一个字符,然后去从之前的dp数组存储的中间状态取值,一定是只退text1和text2其中一个的,容易的思维误区是两个字符串要回退一个字符。