动态规划二——简单多状态dp问题

目录

题目一——面试题 17.16. 按摩师 - 力扣(LeetCode)

题目二—— 213. 打家劫舍 II - 力扣(LeetCode)

题目三——740. 删除并获得点数 - 力扣(LeetCode) 

题目四——LCR 091. 粉刷房子 - 力扣(LeetCode)

题目五——309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode) 

题目六——714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode) 

题目七——123. 买卖股票的最佳时机 III - 力扣(LeetCode)

 7.1.解法一

7.2.解法二

题目八——188. 买卖股票的最佳时机 IV - 力扣(LeetCode) 


 

题目一——面试题 17.16. 按摩师 - 力扣(LeetCode)

9143e325652547a98718c92c44e95499.png

f980932072d94f60aca262743cba1885.png

我们的动态规划还是按照下面五步来解决!!

1.状态表示

一般而言,我们都是根据【经验+题目要求】得出这个状态表示的,一般而言就是下面这两种

  1. 从某一个位置开始,巴拉巴拉
  2. 从起始位置开始到某一个位置,然后巴拉巴拉

我们发现我们都是从左往右退导的,所以我们选择第二种方式来表示

这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:

  • 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]);
    }
};

810e259ac3ed4b72a479d5788f6ac2de.png

题目二—— 213. 打家劫舍 II - 力扣(LeetCode)

8d09d012dbaf42438e8196434a2e7af2.png

fbfab6513599461db8f5598850f528e5.png

这题其实和上面那题差不多了!!!

我们的动态规划还是按照下面五步来解决!!

1.状态表示

一般而言,我们都是根据【经验+题目要求】得出这个状态表示的,一般而言就是下面这两种

  1. 从某一个位置开始,巴拉巴拉
  2. 从起始位置开始到某一个位置,然后巴拉巴拉

我们发现我们都是从左往右退导的,所以我们选择第二种方式来表示

这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:

  • 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.注意点

我们上面五步就实现了一个线性的打家劫舍,但是这够吗?不要忘了题目说的

  1. 第一个房屋和最后一个房屋是紧挨着的。
  2. 如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。 

上⼀个问题是⼀个「单排」的模式,这⼀个问题是⼀个「环形」的模式,也就是⾸尾是相连的。

但是我们可以将「环形」问题转化为「两个单排」问题:

首先我们将上面那些整合成一个函数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]);
    }
};

ce63c7ba142542999767a8d9a6fcf0aa.png

其实还有第二种写法 

class Solution {
public:
    int rob(vector<int>& nums) {
         int n = nums.size();
        
        // 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值