LeetCode第6&7天 | 动态规划 | 20220723&20220724
动态规划
本文章参考了许多他人的笔记,仅供自己学习复习使用。
leetcode官网
目录
139. 单词拆分
1.1 读题
1.2 解题
#include <unordered_set>
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string>wordSet;
for(int i = 0; i < wordDict.size(); i++) {
wordSet.insert(wordDict[i]);
}
int len = s.length();
// 状态定义: 以s[i]结尾的子字符串是否符合题意
vector<bool> dp(len);
dp[0] = true;
for(int right = 0; right < len; right++){
// 分类讨论1-不拆分
if(wordSet.count(s.substr(0, right+1))){
dp[right] = true;
continue;
}
// 分类讨论2-拆分
for(int left = right-1;left >= 0; left--){
if(wordSet.count(s.substr(left+1,right+1)) && dp[left]){
dp[right] = true;
break;
}
}
}
return dp[len-1];
}
};
152.乘积最大子序列(中等)
2.1 读题
2.2 解题
参考:liweiwei的解答 思路按照liweiwei的,改写了c++代码。以下记录仅供自己学习复习使用。
-
第一步:状态定义
数组的动态规划问题、子序列、连续子序列的一个通用的状态设置是:
以索引i
结尾的连续子序列的乘积的最大值,用dp[i]
表示 -
第二步:推导状态转移方程
把整个 dp 数组看一遍求最大值即可。因此状态转移方程可能是:
dp[i] = max(dp[i - 1] * nums[i], nums[i])
说明:牢记状态的设置,一定以索引 i 结尾,即乘积数组中 nums[i] 必须被选取。
但本题,dp[i-1]可能为正数或负数,nums[i]也可能为正数或负数,会导致情况十分复杂,数学经验告诉我们这里需要分类讨论,其实就是在提醒我们状态不够用了。因此,需要在原来的一维 dp 后面新增一个状态:
dp[i][1]
表示:以 nums[i] 结尾的连续子序列的乘积的最大值;
dp[i][0]
表示:以 nums[i] 结尾的连续子序列的乘积的最小值。 -
第三步:思考初始化
最开始肯定要算上第一个数,nums[0]本身即为初试的最大值。
所以在函数最开始要补充:当nums数组长度为0时,函数直接返回0。 -
第四步:思考输出
不知道怎么讲,粗鄙的说法就是整个动态规划的过程是断开的,并不像买卖股票系列一直串起来,最后直接输出最后一个元素的值即可。
本题还需要再动态规划结束后,遍历一次dp数组,找出max{dp[i][1]}(i = 1,2,3…n-1)。
代码2.1
class Solution {
public:
int maxProduct(vector<int>& nums) {
int len = nums.size();
if(len == 0) return 0;
// definition of status
vector<vector<int>> dp(len,vector<int>(2));
// initialization:
dp[0][0] = nums[0];
dp[0][1] = nums[0];
for(int i=1;i<len;i++){
if(nums[i]>=0){
dp[i][1]= max(dp[i-1][1]*nums[i],nums[i]);
dp[i][0] = min(dp[i-1][0]*nums[i],nums[i]);
}else{
dp[i][1] = max(dp[i-1][0]*nums[i],nums[i]);
dp[i][0] = min(dp[i-1][1]*nums[i],nums[i]);
}
}
int res = dp[0][1];
for(int i =1; i<len;i++){
res = max(res,dp[i][1]);
}
return res;
}
};
- 第五步:思考空间优化
【因为】当天只与i-1的状态有关,【所以】考虑滚动数组
代码2.2(待修正)
这两天好奇怪,怎么总是手动模拟没问题,但就是会解答出错…
解答出错的示例为nums = [-4,-3,-2] 希望能早日得到解决。
class Solution {
public:
int maxProduct(vector<int>& nums) {
int len = nums.size();
if(len == 0) {
return 0;
}
// definition of status
vector<vector<int>> dp(len,vector<int>(2));
// initialization:
dp[0][0] = nums[0];
dp[0][1] = nums[0];
int res = -2*100000;
int flag = 0;
for(int i=1;i<len;i++){
flag = 1;
if(nums[i]>=0){
dp[i/2][1]= max(dp[(i-1)/2][1]*nums[i],nums[i]);
dp[i/2][0] = min(dp[(i-1)/2][0]*nums[i],nums[i]);
}else{
dp[i/2][1] = max(dp[(i-1)/2][0]*nums[i],nums[i]);
dp[i/2][0] = min(dp[(i-1)/2][1]*nums[i],nums[i]);
}
if(dp[i/2][1]>res){
res = dp[i/2][1];
}
}
if(!flag){
res = nums[0];
}
return res;
}
};
打家劫舍系列
198.打家劫舍
代码1:定义成一维表格的动态规划
class Solution{
public:
int rob(vector<int>& nums){
if(nums.empty()){
return 0;
}
int n = nums.size();
if(n ==1){
return nums[0];
}
vector<int> dp(n);
// 初始化
// 只有一间房,则必偷
dp[0]=nums[0];
// 第二间:若偷,则nums[1]; 若不偷,则dp[0]
dp[1]=max(nums[1], dp[0]);
for(int i = 2;i<n;i++){
//动态转移:
// 若偷i,则不偷i-1,为dp[i-2]+nums[i]
// 若不偷i,则直接按dp[i-1](不需要管到底i-1间偷了没偷)
dp[i] = max(dp[i-2]+nums[i], dp[i-1]);
}
return dp[n-1];
}
};
代码2:定义成二维表格的动态规划
liweiwei的思路:设置哨兵并进行偏移
class Solution{
public:
int rob(vector<int>& nums){
if(nums.empty()){
return 0;
}
int len = nums.size();
if(len ==1){
return nums[0];
}
// 0 表示不偷
// 1 表示偷
// 多加1天表示哨兵,相应地要做一些偏移
vector<vector<int>> dp(len+1,vector<int>(2));
for(int i = 1; i<len+1;i++){
// 不偷
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
// 偷
// 注意这里有个偏移
dp[i][1] = dp[i-1][0] + nums[i-1];
}
return max(dp[len][0],dp[len][1]);
}
};
213. 打家劫舍 II(中等)
这题比打家劫舍多了一个房子们首尾相连。触动警报的方式还是一样。
思路:考虑将n个房间编号为0~n-1
1)若不偷n,则可对[0,1,…,n-2]进行198.打家劫舍一样的解答;
2)若不偷0,则可对[1,2,…,n-1]进行198.打家劫舍一样的解答;
对以上两种情况分别进行198.打家劫舍,然后取两者的最大值,即为最终答案。
代码1:
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(nums.empty())
return 0;
else if(nums.size() == 1)
return nums[1];
else if(nums.size() ==2)
return max(nums[0],nums[1]);
vector<int> dp(n);
int start_i=0, end_i = n-2;
dp[0] = nums[start_i];
dp[1] = max(dp[0], nums[start_i + 1]);
for(int i = start_i + 2; i<= end_i; i++){
dp[i]=max(dp[i-2]+nums[start_i + i], dp[i-1]);
}
int temp = dp[end_i];
dp = vector<int>(nums.size());
start_i=1; end_i = n-1;
dp[0] = nums[start_i];
dp[1] = max(dp[0], nums[start_i + 1]);
for(int i = start_i + 2; i<= end_i; i++){
dp[i]=max(dp[i-2]+nums[start_i + i], dp[i-1]);
}
return max(temp, dp[end_i]);
}
};