目录
题目一——面试题 17.16. 按摩师 - 力扣(LeetCode)
题目二—— 213. 打家劫舍 II - 力扣(LeetCode)
题目三——740. 删除并获得点数 - 力扣(LeetCode)
题目四——LCR 091. 粉刷房子 - 力扣(LeetCode)
题目五——309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)
题目六——714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)
题目七——123. 买卖股票的最佳时机 III - 力扣(LeetCode)
题目八——188. 买卖股票的最佳时机 IV - 力扣(LeetCode)
题目一——面试题 17.16. 按摩师 - 力扣(LeetCode)
我们的动态规划还是按照下面五步来解决!!
1.状态表示
一般而言,我们都是根据【经验+题目要求】得出这个状态表示的,一般而言就是下面这两种
- 从某一个位置开始,巴拉巴拉
- 从起始位置开始到某一个位置,然后巴拉巴拉
我们发现我们都是从左往右退导的,所以我们选择第二种方式来表示
这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:
- dp[i] 表⽰:从起始位置开始,选择到 i 位置时,此时的最⻓预约时⻓。
但是我们单单使用一个dp数组就能解决问题吗?显然是不能的,我们这回得使用两个数组了。
我们这个题在 i 位置的时候,会⾯临「选择」或者「不选择」两种抉择,所依赖的状态需要 细分:
- f[i] 表⽰:从起始位置开始,选择到 i 位置时, nums[i] 必选,此时的最⻓预约时⻓;
- g[i] 表⽰:从起始位置开始,选择到 i 位置时, nums[i] 不选,此时的最⻓预约时⻓。
2.状态转移⽅程:
因为状态表⽰定义了两个,因此我们的状态转移⽅程也要分析两个:
对于 f[i] :
- 如果 nums[i] 必选,则这个nums[i-1]是不能选的。那么我们仅需知道 i - 1 位置在不选的情况下的最⻓预约时⻓, 然后加上 nums[i] 即可,因此 f[i] = g[i - 1] + nums[i] 。
对于 g[i] :
- 如果 nums[i] 不选,那么 i - 1 位置上选或者不选都可以。因此,我们需要知道 i - 1 位置上选或者不选 两种情况下的最⻓预约时⻓,因此g[i] = max(f[i - 1], g[i - 1])
3. 初始化:
这道题的初始化⽐较简单,因此⽆需加辅助节点,仅需初始化f[0] = nums[0], g[0] = 0 即可。
4. 填表顺序
根据「状态转移⽅程」得「从左往右,两个表⼀起填」。
5. 返回值
根据「状态表⽰」,应该返回 max(f[n - 1], g[n - 1]) 。
我们很快就能写出答案
class Solution {
public:
int massage(vector<int>& nums) {
int m=nums.size();
if(m == 0) return 0; // 处理边界条件
//1.创建dp表
vector<int>f(m);
vector<int>g(m);
//2.初始化
f[0]=nums[0];
g[0]=0;
//3.填表
for(int i=1;i<m;i++)
{
f[i]=g[i-1]+nums[i];
g[i]=max(f[i-1],g[i-1]);
}
//4.返回值
return max(f[m-1],g[m-1]);
}
};
题目二—— 213. 打家劫舍 II - 力扣(LeetCode)
这题其实和上面那题差不多了!!!
我们的动态规划还是按照下面五步来解决!!
1.状态表示
一般而言,我们都是根据【经验+题目要求】得出这个状态表示的,一般而言就是下面这两种
- 从某一个位置开始,巴拉巴拉
- 从起始位置开始到某一个位置,然后巴拉巴拉
我们发现我们都是从左往右退导的,所以我们选择第二种方式来表示
这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:
- dp[i] 表⽰:从起始位置开始,选择到 i 位置时,此时偷窃到的最大金额数。
但是我们单单使用一个dp数组就能解决问题吗?显然是不能的,我们这回得使用两个数组了。
我们这个题在 i 位置的时候,会⾯临「偷窃」或者「不偷窃」两种抉择,所依赖的状态需要 细分:
- f[i] 表⽰:从起始位置开始,选择到 i 位置时, nums[i] 必偷窃,此时的偷窃到的最大金额数;
- g[i] 表⽰:从起始位置开始,选择到 i 位置时, nums[i] 不偷窃,此时的偷窃到的最大金额数。。
2.状态转移⽅程:
因为状态表⽰定义了两个,因此我们的状态转移⽅程也要分析两个:
对于 f[i] :
- 如果 nums[i] 必偷窃,则这个nums[i-1]是不能偷的。那么我们仅需知道 i - 1 位置在不偷的情况下的偷窃到的最大金额数, 然后加上 nums[i] 即可,因此 f[i] = g[i - 1] + nums[i] 。
对于 g[i] :
- 如果 nums[i] 不偷窃,那么 i - 1 位置上偷或者不偷都可以。因此,我们需要知道 i - 1 位置上偷或者不偷 两种情况下的偷窃到的最大金额数,因此g[i] = max(f[i - 1], g[i - 1])
3. 初始化:
这道题的初始化⽐较简单,因此⽆需加辅助节点,仅需初始化f[0] = nums[0], g[0] = 0 即可。
4. 填表顺序
根据「状态转移⽅程」得「从左往右,两个表⼀起填」。
5. 返回值
根据「状态表⽰」,应该返回 max(f[n - 1], g[n - 1]) 。
6.注意点
我们上面五步就实现了一个线性的打家劫舍,但是这够吗?不要忘了题目说的
- 第一个房屋和最后一个房屋是紧挨着的。
- 如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
上⼀个问题是⼀个「单排」的模式,这⼀个问题是⼀个「环形」的模式,也就是⾸尾是相连的。
但是我们可以将「环形」问题转化为「两个单排」问题:
首先我们将上面那些整合成一个函数int rob1(vector<int>&nums,int left,int right)
- 偷第⼀个房屋时的最⼤⾦额 x ,此时不能偷最后⼀个房⼦,因此就是偷 [0, n - 2] 区间的房⼦,此时偷窃到的最大金额数就是rob1(0,n-2)。
- 不偷第⼀个房屋时的最⼤⾦额 y ,此时可以偷最后⼀个房⼦,因此就是偷 [1, n - 1] 区间的房⼦,此时偷窃到的最大金额数就是rob1(1,n-1)
- 两种情况下的「最⼤值」,即max(rob1(0,n-2) , rob1(1,n-1) ),就是最终的结果。
因此,问题就转化成求「两次单排结果的最⼤值」。
我们很快就能写出代码
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
//处理只有一间屋子的情况
if(n == 1)
return nums[0];
// 两种情况下的最⼤值
return max(rob1(nums, 0, n - 2), rob1(nums, 1, n - 1));
}
int rob1(vector<int>&nums,int left,int right)
{
if(left>right) return 0; // 处理边界条件
int m=nums.size();
//1.创建dp表
vector<int>f(m);
vector<int>g(m);
//2.初始化
f[left] = nums[left];
//3.填表
for(int i=left+1;i<=right;i++)
{
f[i]=g[i-1]+nums[i];
g[i]=max(f[i-1],g[i-1]);
}
//4.返回值
return max(f[right],g[right]);
}
};
其实还有第二种写法
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
//