(1) NC127 最长公共子串
定义dp[i][j] 表 示 字 符 串 str1 中 第 i 个 字 符 和 str2 种 第 j 个 字 符 为 最 后 一 个 元 素 所 构 成 的 最长公共子串。 如 果 要 求 dp[i][j] , 也 就 是 str1 的 第 i 个 字 符 和 str2 的 第 j 个 字 符 为 最 后 一 个元素所构成的最长公共子串,我们首先需要判断这两个字符是否相等。
如果不相等,那么他们就不能构成公共子串,也就是
dp[i][j]=0;
如 果 相 等 , 我 们 还 需 要 计 算 前 面 相 等 字 符 的 个 数 , 其 实 就 是 dp[i-1][j-1] , 所 以 dp[i][j]=dp[i-1][j-1]+1;
在代码中,我们用dp[1][1]表示str1中的第1个元素和str2中的第1个元素所构成的最长公共子串。
class Solution {
public:
/**
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
string LCS(string str1, string str2) {
// write code here
vector<vector<int>> res(str1.length()+1, vector<int>(str2.length()+1, 0));
int maxLen = 0;//表示最长公共子串的长度
int LastIndex = 0;//表示最长公共子串最后一个元素在str1中是第几个元素
//i表示 str1中的第i个元素
for(int i = 0; i < str1.length(); i++)
{
for(int j = 0; j < str2.length();j++)
{
if(str1[i] == str2[j])
{
res[i+1][j+1] = res[i][j] + 1;
if(res[i+1][j+1] > maxLen)
{
maxLen = res[i+1][j+1];
LastIndex = i;
}
}
else
{
res[i+1][j+1] = 0;
}
}
}
string str;
if(maxLen == 0) return "-1";
else
{
for(int i = maxLen - 1; i>=0; i--)
{
str += str1[LastIndex - i];
}
}
return str;
}
};
进行空间优化,采用滚动数组,j要逆序遍历,为什么逆序,参考上一篇博客背包问题。
class Solution {
public:
/**
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
string LCS(string str1, string str2) {
// write code here
vector<int> res(str2.length()+1, 0);
int maxLen = 0;//表示最长公共子串的长度
int LastIndex = 0;//表示最长公共子串最后一个元素在str1中是第几个元素
//i表示 str1中的第i个元素
for(int i = 0; i < str1.length(); i++)
{
for(int j = str2.length() -1; j >= 0;j--)
{
if(str1[i] == str2[j])
{
res[j+1] = res[j] + 1;
if(res[j+1] > maxLen)
{
maxLen = res[j+1];
LastIndex = i;
}
}
else
{
res[j+1] = 0;
}
}
}
string str;
if(maxLen == 0) return "-1";
else
{
for(int i = maxLen - 1; i>=0; i--)
{
str += str1[LastIndex - i];
}
}
return str;
}
};
(2) leeetcode139. 单词拆分 https://leetcode-cn.com/problems/word-break/
dp[i] 表 示 字 符 串 的 前 i 个 字 符 经 过 拆 分 是 否 都 存 在 于 字 典 wordDict 中 。 如 果 要
求 dp[i] , 我 们 需 要 往 前 截 取 k 个 字 符 , 判 断 子 串 [i-k+1 , i] 是 否 存 在 于 字 典 wordDict中,并且前面[0,i-k]子串拆分的子串也是否都存在于wordDict中。
s.substr(j, i - j) j表示子串的起始索引位置 i - j为子串的长度
即要判断前i个字符是否可以拆分成几个字典中存在的单词:转化为 判断前j个字符是否可以拆分成字典中存在的单词&& (j 到i 的子串是否存在与字典中)
代码如下:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<string> a;//将字典中的单词存入set中便于查找
for(int i = 0; i < wordDict.size(); i++)
{
a.insert(wordDict[i]);
}
int len = s.length();
vector<int> res(len+1, 0);
res[0] = 1;
for(int i = 1; i <= len; i++)
{
for(int j = 0; j < i; j++)
{
//这里的res[j]表示前j个字符, 正好对应索引从0到j-1
if(res[j]==1 && a.find(s.substr(j, i - j))!= a.end())//
{
res[i] = 1;
break;//直接跳出内层的for循环
}
}
}
return res[len];
}
};
(3) leetcode 1049. 最后一块石头的重量 II
https://leetcode-cn.com/problems/last-stone-weight-ii/ (转化为0-1背包问题)
这题是让每次从数组中任意选择两个石头,让他们相互销毁,求最终剩下的一块石头最小
可能重量,如果没有剩下,则返回0。直接计算可能不太好计算,我们这样来思考一下
这题相当于一群战斗力各不相同的人相互厮杀,如果战斗力相同他们就同归于尽,如果战
斗力不同,战斗力小的死去,战斗力高的活下来,但战斗力高的战斗值要减去战斗力小的
值。最后如果他们都能同归于尽最好不过了,如果不能都同归于尽,最后会剩下一个人,
他的战斗值越小越好。
所以这题相当于把一群战斗力各不相同的人分为两组,每组战斗力总和相差越小越好,也
就是把数组分为两部分,这两部分的差值尽可能小。
解题思路如下
我 们 首 先 计 算 数 组 中 所 有 元 素 的 和 , 比 如 是 sum , 然 后 把 它 分 为 两 部 分 , 如 果 sum 是 偶数, 那 么 这 两 部 分 的 值 都 是 sum/2 , 如 果 sum 是 奇数, 则 一 部 分 是 sum/2 , 另 一 部 分 是sum/2+1;我们取较小的sum/2。
解 题 思 路 就 变 成 了 从 数 组 中 选 择 一 些 元 素 , 让 他 们 的 和 尽 可 能 的 接 近 sum/2 , 经 过 我 们的不断分析,所以这题就变成了经典的基础背包问题,也就是说背包的容量是sum/2 ,让我们从数组选选择一些元素放到背包中,求背包所能容纳的最大价值。
定义dp[i][j] 表 示 前 i 个 元 素 放 到 背 包 容 量 为 j 的 背 包 中 , 所 能 获 取 的 最 大 价 值 , 当 我 们 选择第i个元素stones[i-1]的时候。
如果stones[i-1]<=j,说明第i个元素能放到背包中,我们可以选择放也可以选择不放,
如果选择放那么
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
for(int i = 0; i < stones.size(); i++)
{
sum += stones[i];
}
int capability = sum>>1;
vector<vector<int>> dp(stones.size()+1, vector<int>(capability + 1, 0));
for(int i = 1; i <= stones.size(); i++)
{
for(int j = 1; j <= capability; j++)
{
if(j - stones[i - 1] >= 0)
{
dp[i][j] = max(dp[i-1][j], dp[i-1][j - stones[i - 1]] + stones[i - 1]);
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
return (sum - dp[stones.size()][capability] * 2);
}
};
进行空间优化
class Solution {
public:
int lastStoneWeightII(vector<int>& stones) {
int sum = 0;
for(int i = 0; i < stones.size(); i++)
{
sum += stones[i];
}
int capability = sum>>1;
vector<int> dp(capability + 1, 0);
for(int i = 1; i <= stones.size(); i++)
{
for(int j = capability; j >= 1; j--)
{
if(j - stones[i - 1] >= 0)
{
dp[j] = max(dp[j], dp[j - stones[i - 1]] + stones[i - 1]);
}
else
{
dp[j] = dp[j];
}
}
}
return (sum - dp[capability] * 2);
}
};
(4) leetcode第1035题 不想交的线
思路:设dp[i][j] 表示nums1 中前i个元素和nums2中前j个元素构成的最多的不相交的线的数量。
如 果 要 求 dp[i][j] , 我 们 首 先 判 断 nums1 的 第 i 个 元 素 和 nums2 的 第 j 个 元 素 是 否 相
等 如果 相 等 , 说 明 nums1 的 第 i 个 元 素 可 以 和 nums2 的 第 j 个 元 素 可 以 连 成 一 条 线 , 这 个 时候 nums1 的 前 i 个 元 素 和 nums2 的 前 j 个 元 素 所 能 绘 制 的 最 大 连 接 数 就 是 nums1 的 前 i-1个 元 素 和 nums2 的 前 j-1 个 元 素 所 能 绘 制 的 最 大 连 接 数 加1 , 也 就 是 dp[i][j]=dp[i-1][j-1]+1; 如果不相等,我们就把nums1去掉一个元素,计算nums1的前i-1个元素和nums2的前j 个 元 素 能 绘 制 的 最 大 连 接 数 , 也 就 是 dp[i-1][j] , 或 者 把 nums2 去 掉 一 个 元 素 , 计 算nums2的前j-1个元素和nums1的前i个元素能绘制的最大连接数,也就是dp[i][j-1] ,
这两种情况我们取最大的即可,所以我们可以找出递推公式
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size()+1, vector<int>(nums2.size()+1, 0));
for(int i = 1; i <= nums1.size(); i++)
{
for(int j = 1; j <= nums2.size(); j++)
{
if(nums1[i - 1] == nums2[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[nums1.size()][nums2.size()];
}
};
(5) leetcode62. 不同路径 https://leetcode-cn.com/problems/unique-paths/
思路:定义dp[i][j]表示机器人到达网格中(i, j)位置,总共的路径数。
注 意: 这 里 机 器 人 只 能 向 下 和 向 右 移 动 , 不 能 往 其 他 方 向 移 动 , 我 们 用 dp[i][j] 表 示 到 坐标(i,j)这个格内有多少条不同的路径,所以最终的答案就是求dp[m-1][n-1]。
因为只能从上面或左边走过来,所以递推公式是
dp[i][j] = dp[i-1][j] + dp[i][j-1]。
dp[i-1][j]表示的是从上面走过来的路径条数。
dp[i][j-1]表示的是从左边走过来的路径条数。
初始化:第一行和第一列中的位置的路径数都是1.
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, 0));
//初始化第一行和第一列的值
for(int i = 0; i < m; i++) dp[i][0] = 1;
for(int i = 0; i < n; i++) dp[0][i] = 1;
for(int i = 1;i < m; i++)
{
for(int j = 1; j < n; j++)
{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
空间优化:
由dp[i][j] = dp[i-1][j] + dp[i][j-1]可知,dp[i][j]依赖同行前面的值,并且依赖上一行同列的值。因此,j不能采用逆序来遍历,应该采用正序来遍历。
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n, 0);
//初始化第一行和第一列的值
for(int i = 0; i < n; i++) dp[i] = 1;
for(int i = 1;i < m; i++)
{
for(int j = 1; j < n; j++)
{
dp[j] = dp[j] + dp[j-1];
}
}
return dp[n-1];
}
};
(6) leetcode 63. 不同路径 II 在上一题的基础上,加上了障碍物。https://leetcode-cn.com/problems/unique-paths-ii/
思路:在初始化第一行和第一列的值时考虑障碍物,另外,在计算其他值时同样考虑障碍物的影响
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
vector<vector<int>> dp(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), 0));
//初始化第一行的值 初始化时要考虑障碍物
int flag1 = 0; //表示有没有障碍物出现
for(int i = 0; i < obstacleGrid.size(); i++)
{
if(flag1 == 0 && obstacleGrid[i][0] == 0)
{
dp[i][0] = 1;
}
else if(flag1 == 0 && obstacleGrid[i][0] == 1)
{
dp[i][0] = 0;
flag1 = 1;
}
else dp[i][0] = 0;
}
//初始化第一列的值 初始化时要考虑障碍物
int flag2 = 0;
for(int i = 0; i < obstacleGrid[0].size(); i++)
{
if(flag2 == 0 && obstacleGrid[0][i] == 0)
{
dp[0][i] = 1;
}
else if(flag2 == 0 && obstacleGrid[0][i] == 1)
{
dp[0][i] = 0;
flag2 = 1;
}
else dp[0][i] = 0;
}
//计算其他位置的值
for(int i = 1;i < obstacleGrid.size(); i++)
{
for(int j = 1; j < obstacleGrid[0].size(); j++)
{
if(obstacleGrid[i][j] == 1) //考虑障碍物
{
dp[i][j] = 0;
}
else dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[obstacleGrid.size()-1][obstacleGrid[0].size()-1];
}
};
(7) 剑指offer剑指 Offer 47. 礼物的最大价值
https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof/
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
//行数
int m = grid.size();
//列数
int n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = grid[0][0];
//初始化第1列的价值
for(int i = 1; i < m; i++) dp[i][0] = dp[i -1][0] + grid[i][0];
//初始化第1行的价值
for(int i = 1; i < n; i++) dp[0][i] = dp[0][i-1] + grid[0][i];
for(int i = 1;i < m; i++)
{
for(int j = 1; j < n; j++)
{
dp[i][j] = ((dp[i-1][j] > dp[i][j-1])? dp[i-1][j] : dp[i][j-1]) + grid[i][j];
}
}
return dp[m-1][n -1];
}
};
(8) leetcode516. 最长回文子序列
https://leetcode-cn.com/problems/longest-palindromic-subsequence/
求的是最长回文子序列
我们定义dp[i][j]表示字符串中从i到j之间的最长回文子序列。
1,如果s.charAt(i) == s.charAt(j),也就是说两头的字符是一样的,他们可以和中间
的最长回文子序列构成一个更长的回文子序列,即 dp[i][j] = dp[i+1][j-1] + 2
如 果 s.charAt(i) != s.charAt(j) , 说 明 i 和 j 指 向 的 字 符 是 不 相 等 的 , 我 们 可 以 截
取,要么去掉i指向的字符,要么去掉j指向的字符,然后取最大值,即
dp[i][j] = Math.max(dp[i][j-1], dp[i+1][j])
初始化:一个字符也是回文串,即dp[i][i]=1;注意二维数组dp[i][j]的定义,如果i>j是没有意义的。
从 上 面 图 中 可 以 看 出 如 果 我 们 想 求 dp[i][j] , 那 么 其 他 3 个 必 须 都 是 已 知 的 , 很 明 显 从 上往下遍历是不行的,我们只能让i从最后一个字符往前遍历,j从i的下一个开始遍历,最后只需要返回dp[0][length - 1]即可。
class Solution {
public:
int longestPalindromeSubseq(string s) {
int len = s.length();
vector<vector<int>> dp(len+1, vector<int>(len+1, 0));
for(int i = len - 1; i >=0; i-- )
{
dp[i][i] = 1;
for(int j = i+1; j < len; j++)
{
if(s[i] == s[j])
{
dp[i][j] = dp[i+1][j-1] + 2;
}
else
{
dp[i][j] = max(dp[i +1][j], dp[i][j -1]);
}
}
}
return dp[0][len - 1];
}
};
(9) leetcode5 最长回文子序列 https://leetcode-cn.com/problems/longest-palindromic-substring/
中心扩展法+ 动态规划
方法一:中心扩展法
本题最容易想到的一种方法应该就是 中心扩散法。
中心扩散法怎么去找回文串?
从每一个位置出发,向两边扩散即可。遇到不是回文的时候结束。举个例子,str = acdbbdaastr=acdbbdaa 我们需要寻找从第一个 b(位置为 33)出发最长回文串为多少。怎么寻找?
首先往左寻找与当期位置相同的字符,直到遇到不相等为止。
然后往右寻找与当期位置相同的字符,直到遇到不相等为止。
最后左右双向扩散,直到左和右不相等。如下图所示:
class Solution {
public:
string longestPalindrome(string s) {
//中心扩展法
int len = s.length();
if(len < 2) return s;
int maxLen = 1; //回文字符串的最长长度
int start = 0; //最长回文字符串的起始位置
int left = 0, right = 0, tempLen = 1; //临时变量用来记录不同中心字符得到的最长回文子串的信息
for(int i = 0; i < len; i++)
{
left = i - 1;
right = i + 1;
//找到左边与s[i]相同的 并将left往左移
while(left>=0&& s[left] == s[i]) left--,tempLen++;
while(right < len && s[right] == s[i]) right++, tempLen++;
while(left>=0 && right < len && s[left] == s[right])
{
left--;
right++;
tempLen+=2;
}
if(tempLen > maxLen)
{
maxLen = tempLen;
start = left + 1;//因为left多减了1 因此要加上
}
tempLen = 1; //注意注意::: 因为tempLen用来记录不同中心字符的最长回文子串长度,
// 因此,每计算完一个字符,其值要恢复初始值
}
return s.substr(start, maxLen);
}
};
方法二:中心扩展法+动态规划
当利用动态规划时,dp[left][right]表示从 left 到 right组成的子串是否是回文字符串。
如果s[left] == s[right] 并且 ( right - left <=2 || dp[left+1][right -1] 为回文字符串时 ),
dp[left][right] = true;
这里 s[left] == s[right]还不够判断dp[left][right] 为回文字符串,还需要判断 left+1 到right - 1 的字符串。 既取决于dp[left+1][right -1], 另外当right - left <= 2时,也可以判定其为回文字符串。
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
if(len < 2) return s;
int maxLen = 1; //此时,一定要初始化为1 表示回文子串的长度
int start = 0; //表示回文子串的起始位置对应的索引
//定义状态数组
vector<vector<bool>> dp(len + 1, vector<bool>(len + 1, false));
for(int right = 1; right < len; right++)
{
for(int left = right - 1; left >= 0; left--)
{
//此处的判定条件比较关键
if((s[left] == s[right]) && ((right - left <=2 || dp[left + 1][right - 1] == true)))
{
dp[left][right] = true;
//此时,比较回文子串是否是最长的。
if(right - left + 1 > maxLen)
{
maxLen = right - left + 1;
start = left;
}
}
}
}
return s.substr(start, maxLen);
}
};
(10) leetcode122. 买卖股票的最佳时机 II
定义状态:
dp[i][0] :表示第i天手上没有股票时的最大利润。
第i天手上没有股票,可能是以下两种情况:
(1) 第i-1 天手上没有股票, 第i天也没有参与任何交易 即dp[i-1][0]
(2) 第i-1天 手上有股票,在第i天把股票都卖出去了即 dp[i-1][1] + price[i]
取两种情况的最大值。
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][0] :表示第i天手上有股票时的最大利润。
第i天手上有股票,可能是以下两种情况:
(1) 第i-1 天手上没有股票, 第i天买入股票 即dp[i-1][0] - price[i]
(2) 第i-1天 手上有股票,在第i天没有交易即 dp[i-1][1]
取两种情况的最大值。
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
最终的结果,应该为dp[i][0] i为最后一天。
初始化:dp[0][0] = 0 dp[0][1] = -price[0]
class Solution {
public:
int maxProfit(vector<int>& prices) {
int size = prices.size();
if(size < 2) return 0;
vector<vector<int>> dp(size, vector(2, 0));
dp[0][0] = 0, dp[0][1] = -prices[0];
for(int i = 1; i < size; i++)
{
dp[i][0] = dp[i-1][0] > (dp[i-1][1] + prices[i])? dp[i-1][0] : (dp[i-1][1] + prices[i]);
dp[i][1] = dp[i-1][1] > (dp[i-1][0] - prices[i])? dp[i-1][1] : (dp[i-1][0] - prices[i]);
}
return dp[size - 1][0];
}
};
上 面 计 算 的 时 候 我 们 看 到 当 天 的 利 润 只 和 前 一 天 有 关 , 没 必 要 使 用 一 个 二 维 数 组 , 只 需要 使 用 两 个 变 量 , 一 个 记 录 当 天 交 易 完 之 后 手 里 持 有 股 票 的 最 大 利 润 , 一 个 记 录 当 天 交易完之后手里没有股票的最大利润.
class Solution {
public:
int maxProfit(vector<int>& prices) {
int size = prices.size();
if(size < 2) return 0;
int noGu = 0, youGu = -prices[0];
for(int i = 1; i < size; i++)
{
noGu = noGu > (youGu + prices[i])? noGu : (youGu + prices[i]);
youGu = youGu > (noGu - prices[i])? youGu : (noGu - prices[i]);
}
return noGu;
}
};
11. leetcode 121. 买卖股票的最佳时机
方法一:采用双指针left right 当prices[left] > prices[right]时, left = right; right++;
否则,更新最大收益,right++;
class Solution {
public:
int maxProfit(vector<int>& prices) {
int left = 0, right = 1;
int res = 0;
int size = prices.size();
while(right < size)
{
if(prices[left] > prices[right])//说明此时肯定会赔钱
{
left = right;
right++;
}
else
{
res = (res > (prices[right] - prices[left]))? res: (prices[right] - prices[left]);
right++;
}
}
return res;
}
};
12. 买卖股票的最佳时机含手续费
与10题基本一样,只不过在卖出股票时要收取手续费
状态转移方程如下:
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
状态数组的初始化不变。
(13) leetcode 198. 打家劫舍
定义dp[ i][0] 表示 第i家不被打劫 可以获得最大利益。此时,第i-1家 可以被打劫也可以不被打劫
dp[i][0] = max(dp[i-1][1], dp[i-1][0] )
定义dp[ i][1] 表示 第i家被打劫 可以获得最大利益。此时,第i-1家 不可以被打劫。
dp[i][1] = dp[i-1][0] + nums[i]
初始化: 第一家没被偷:dp[0][0] = 0; dp[0][1] = nums[0]
public:
int rob(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
vector<vector<int>> dp(size, vector<int>(2, 0));
dp[0][0] = 0;
dp[0][1] = nums[0];
for(int i = 1; i < size; i++)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
return max(dp[size - 1][1], dp[size - 1][0]);
}
};
空间优化:
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
//nums中最左边的元素
int noJie = 0;
int Jie = nums[0];
//数组中剩余元素
for(int i = 1; i < size; i++)
{
int temp = Jie;
Jie = noJie + nums[i];
noJie = max(noJie, temp);
}
return max(noJie, Jie);
}
};
一维数组:dp[i] 表示前i家 能获得的最大收益
状态转移方程: 如果第i家不打劫,dp[i] = dp[i-1]
如果第i家被打劫, 由于被打劫的家庭不能连续,因此,此时 第i - 1家不能被打劫。
dp[i] = dp[i - 2] + nums[ i - 1]
nums[i -1] 是打劫第i家获得的收益
初始化: dp[0] = 0 dp[1] = nums[0]
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
vector<int> dp(size + 1, 0);
dp[0] = 0;
dp[1] = nums[0];
for(int i = 2; i <= size; i++)
{
dp[i] = max(dp[i-1], dp[i - 2] + nums[i -1]);
}
return dp[size];
}
};
利用滚动数组进行优化
class Solution {
public:
int rob(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
int pre = 0;
int cur = nums[0];
int next = 0;
for(int i = 2; i <= size; i++)
{
next = max(cur, pre + nums[i -1]);
pre = cur;
cur = next;
}
return next;
}
};
(14) leetcode213. 打家劫舍 II https://leetcode-cn.com/problems/house-robber-ii/
由于数组是环形,打劫第一家,最后一家就不能打劫。 打劫了最后一家,就不能打劫第一家。
根据第一家是否被打劫,试图将数组拆分。
class Solution {
int helper(vector<int>& nums, int left, int right)
{
//数组中最左边的那家 被打劫和不被打劫的收益
int noJie = 0;
int Jie = nums[left];
for(int i = left + 1; i < right ; i++)
{
int temp = Jie;
Jie = noJie + nums[i];
noJie = max(noJie, temp);
}
return max(noJie, Jie);
}
public:
int rob(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
//第一家被打劫 最后一家不可被打劫 数组拆分为0 到 nums.size() - 1
int robfirst = helper(nums, 0, nums.size() - 1);
//第一家不被打劫 最后一家可以被打劫 数组拆分为 1 到 nums.size()
int noRobFirst = helper(nums, 1, nums.size());
return max(robfirst, noRobFirst);
}
};
(15) leetcode 53. 最大子序和
dp[i] 表示数组nums中前 i+1元素的最大子序列之和
初始化:dp[0] = nums[0]
状态转换方程:对于dp[i] 如果dp[i - 1] > 0 dp[i] = dp[i -1] + nums[i]
如果dp[i - 1] < 0 dp[i] = nums[i]
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
vector<int> dp(size, 0);
int maxSum = nums[0];
dp[0] = nums[0];
for(int i = 1; i < size; i++)
{
dp[i] = (dp[i-1] > 0)? dp[i -1] + nums[i] : nums[i];
maxSum = maxSum > dp[i] ? maxSum: dp[i];
}
return maxSum;
}
};
优化空间,只需要两个变量pre 和cur 来代替 dp数组
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
//vector<int> dp(size, 0);
int maxSum = nums[0];
//dp[0] = nums[0];
int pre = nums[0];
int cur = 0;
for(int i = 1; i < size; i++)
{
cur = (pre > 0)? pre + nums[i] : nums[i];
pre = cur;
maxSum = maxSum > cur ? maxSum: cur;
}
return maxSum;
}
};
(16)leetcode面试题 17.16. 按摩师 与打家劫舍思路一样
(1)动态规划
class Solution {
public:
int massage(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
vector<vector<int>> dp(size, vector(2, 0));
dp[0][0] = 0;
dp[0][1] = nums[0];
for(int i = 1; i < size; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i-1][1]);
dp[i][1] = dp[i-1][0] + nums[i];
}
return max(dp[size - 1][0], dp[size - 1][1]);
}
};
(2)空间优化
class Solution {
public:
int massage(vector<int>& nums) {
int size = nums.size();
if(size == 0) return 0;
if(size == 1) return nums[0];
int JieShou = nums[0];
int NoJieShou = 0;
for(int i = 1; i < size; i++)
{
int temp = NoJieShou;
NoJieShou = max(NoJieShou, JieShou);
JieShou = temp + nums[i];
}
return max(JieShou, NoJieShou);
}
};
(17) leetcode300. 最长递增子序列用dp[i]表示数组的前i个元素构成的最长上升子序列,如果要求dp[i],我们需要用num[i]和前面的数字一个个比较,如果比前面的任何一个数字大,说明加入到他的后面可 以 构 成 一 个 上 升 子 序 列 , 就 更 新 dp[i] 。 我 们 就 以 [8 , 2 , 3 , 1 , 4] 为 例 来 画 个 图 看一下
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int size = nums.size();
if(size <= 1) return size;
int maxLen = 1;
vector<int> dp(size, 1);
for(int i = 1; i < size; i++)
{
for(int j = 0; j < i; j++)
{
if(nums[i] > nums[j])
{
dp[i] = max(dp[i], dp[j] + 1);
maxLen = max(maxLen, dp[i]);
}
}
}
return maxLen;
}
};
(18) leetcode 718. 最长重复子数组
dp[i][j] 表示以nums1[i] 和nums2[j] 为结尾的最长重复子数组。
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int size1 = nums1.size();
int size2 = nums2.size();
if(size1 == 0||size2 == 0) return 0;
vector<vector<int>> dp(size1 + 1, vector<int>(size2 + 1, 0));
int maxlen = 0;
for(int i = 1; i <= size1; i++)
{
for(int j = 1; j <= size2; j++)
{
if(nums1[i - 1] == nums2[j -1])
{
dp[i][j] = dp[i -1][j -1]+ 1;
if(dp[i][j] > maxlen)
{
maxlen = dp[i][j];
}
}
}
}
return maxlen;
}
};
优化空间:
由于dp[i][j] = dp[i -1][j -1]+ 1; 由于dp[i][j] 依赖上一行的j-1,因此,空间优化时,j要逆序遍历。
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int size1 = nums1.size();
int size2 = nums2.size();
if(size1 == 0||size2 == 0) return 0;
vector<int> dp(size2 + 1, 0);
int maxlen = 0;
for(int i = 1; i <= size1; i++)
{
for(int j = size2; j >= 1; j--)
{
if(nums1[i - 1] == nums2[j -1])
{
dp[j] = dp[j -1]+ 1;
}
else//else不能丢
{
dp[j] = 0;
}
if(maxlen < dp[j])
{
maxlen = dp[j];
}
}
}
return maxlen;
}
};