多状态dp问题(C++)


前言

在本文章中,我们将要详细介绍一下有关多状态dp问题的求解。采用动态规划解决。

213. 打家劫舍 II

题目分析

213. 打家劫舍 II

算法原理

1.状态表示

由于房子都是围成一圈的,我们偷了第一个屋子就不能偷取第二个屋子。
我们可以对此进行分类讨论。
⭐️从【0,n-2】位置进行一次打家劫舍
⭐️从【1,n-1】位置进行一次打家劫舍

列出dp表,dp表中值的含义是什么
这可以细分为两个表,因为经过该房间时不确定偷与不偷
  ⭐️ .f[i]表示到达i房间时,资金被偷
  ⭐️.g[i]表示到达i房间时,资金没有被偷

2.状态转移方程

根据最近一步划分问题
  🌟 f[i]:i位置被偷,那么根据题目规定,i-1位置就不能被偷,这不就正好是g[i-1],再加上i位置被偷的资金;
  🌟g[i]:i位置没有被偷,i-1位置我们不确定有没有被偷,所以需要分为两种情况,这两种情况取最大值
    🐧.i-1位置也没有被偷,就是g[i-1]
    🐧.i-1位置被偷了,就是f[i-1]
结论:
  f[i]=g[i-1]+nums[i];
  g[i]=max(g[i-1],f[i-1])

3.初始化

保证填表不越界
  f[1]需要g[0]的值;g[1]需要g[0]和f[0]的值, 所以需要初始化g[0]和f[0].
  不用开辟额外的空间,这道题目的初始化很简单。
注意:数组的下标和边界条件

4.填表顺序

两个表一起填,从左往右

5.返回值是什么

max(f[n-1],g[n-1]);

代码实现

class Solution {
public:
    int n;
    int _rob(vector<int>& nums,int begin,int end) 
    {
        //建表
        vector<int>f(n,0);
        vector<int>g(n,0);
        //初始化
        f[begin]=nums[begin];
        for(int i=begin+1;i<=end;i++)
        {
            f[i]=nums[i]+g[i-1];
            g[i]=max(g[i-1],f[i-1]);
        }
        return max(f[end],g[end]);
    }
    int rob(vector<int>& nums) 
    {
        n=nums.size();
        if(n==0) return 0;
        if(n==1) return nums[0];
        return max(_rob(nums,0,n-2),_rob(nums,1,n-1));
    }
};

740. 删除并获得点数

题目分析

740. 删除并获得点数

算法原理

本题的关键是:
选择一个数之后,删除所有等于 nums[i] - 1和nums[i] + 1 的元素。

这不就和我们上面的题目很相似吗??
选择了这一个,旁边的两个不能选择。

但是这种情况下必须是有序的才好进行操作。
那我们就想办法把它变为有序,不就行了??

如何变化呢??
假设我们有这样一组数据nums【3,2,1,6,4,3】;
我们重开一个数组arr,范围是【0,数组最大值】;
我们就可以通过下标映射这个值,映射结果为:
【0,1,2,6,4,0,6】;
nums中有两个3,映射下来就是6,放到下标为3的位置。

根据数组arr进行一次打家劫舍就可以。
不需要再对数组nums进行操作了。

代码编写

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) 
    {
        //预处理
        //找出nums最大值
        int Max=nums[0];
        for(auto&e:nums)
        {
            Max=max(Max,e);
        }
        vector<int>arr(Max+1,0);
        for(auto&e:nums)
        {
            arr[e]+=e;
        }
        //打家劫舍
        vector<int>f(Max+1,0);
        vector<int>g(Max+1,0);
        
        for(int i=1;i<=Max;i++)
        {
            f[i]=arr[i]+g[i-1];
            g[i]=max(f[i-1],g[i-1]);
        }
        return max(f[Max],g[Max]);
    }
};

309. 买卖股票的最佳时机含冷冻期

在这里插入图片描述

算法原理

1.状态表示

列出dp表,dp表中值的含义是什么
   dp[i]表示第i天之后此时的最大利润
由于第i天不确定具体状态,多状态dp问题
    🌟 .dp[i][0]:手中有股票没有卖出,我们简单称为买入状态,此时的最大利润
    🌟 .dp[i][1]:处于冷冻期,无法购买股票,我们称为冷冻期,此时的最大利润
    🌟 .dp[i][2]:手中没有股票,也不处于冷冻期,此时的最大利润

2.状态转移方程

根据最近一步划分问题

根据状态表示,我们可以划分为9种不同的转换
    🌟 .(i-1)天处于买入状态,第i天处于买入状态:这个是可以的,这一天啥也不干
    🌟 .(i-1)天处于买入状态,第i天处于冷冻期状态:这个可以,就是这天把股票卖了,赚了钱,需要加上prices[i]的值(涉及利润)
    🌟 .(i-1)天处于买入状态,第i天处于正常状态:这个不可以,你手中有股票,不处于正常状态,即使把股票卖了,也得先经过冷冻期才可以
    🌟 .(i-1)天处于冷冻期状态,第i天处于冷冻期状态:这个不可以,冷冻期只能有一天
    🌟 .(i-1)天处于冷冻期状态,第i天处于买入状态:这个不可以,冷冻期是不能买股票的
    🌟 .(i-1)天处于冷冻期状态,第i天处于正常状态:这个可以,经过一天之后进入正常状态。
    🌟 .(i-1)天处于正常状态,第i天处于正常状态:这个可以,感觉这个股票不好,等一等再买
    🌟 .(i-1)天处于正常状态,第i天处于买入状态:这个可以,可以进行购买股票,买股票需要花钱,需要减去股票的钱pricesi
    🌟 .(i-1)天处于正常状态,第i天处于冷冻期状态:这个不可以,冷冻期是在股票卖了之后才进入的

下面有个简图描述上面信息
在这里插入图片描述
箭头方向表示:从(i-1)天到第i天

3.初始化+边界条件

本题初始化比较简单,不需要创建虚拟节点了
dp[0][0]=-prices[0];这一天只买了股票,买是需要花钱的
dp[0][1]=0;买了有紧接着卖了,没有利润
dp[0][2]=0;

4.填表顺序

从左往右

5.返回值是什么

最后一天的最大收益有两种可能,而且一定是“不持有”状态下的两种可能,把这两种“不持有”比较一下大小,返回即可
max(dp[n-1][1],dp[n–1][2]);

代码实现

class Solution {
public:
    int maxProfit(vector<int>& prices) 
    {
        //建表
        int n=prices.size();
        if(n==0)
        {
            return 0;
        }
        vector<vector<int>>dp (n,vector<int>(3));
        //初始化
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        dp[0][2]=0;
        //填表
        for(int i=1;i<n;i++)
        {
            dp[i][0]=max(dp[i-1][0],dp[i-1][2]-prices[i]);
            dp[i][1]=dp[i-1][0]+prices[i];
            dp[i][2]=max(dp[i-1][2],dp[i-1][1]);
        }
        //返回值
        return max(dp[n-1][1],dp[n-1][2]);
    }
};

188. 买卖股票的最佳时机 IV

题目解析

188. 买卖股票的最佳时机 IV

算法原理

1.状态表示

列出dp表,dp表中值的含义是什么
dp[i]:代表在第i天获得的最大利润
这个又可以细分为两种状况:
 😾f[i]:第i天,手上有股票,处于买入状态,此时的最大利润。
 😾g[i]:第i天,手上无股票,处于卖出状态,此时的最大利润。

题目中要求最多完成k笔交易:又可以细分
 😾f[i][j]:在第i天,手上有股票,进行了j次交易,此时的最大利润
 😾g[i][j]:在第i天,手上无股票,进行了j次交易,此时的最大利润

2.状态转移方程

dp[i]是什么,根据最近一步划分问题
多状态可以相互转化,画图
在这里插入图片描述
 😾.在i-1天处于f状态,过了一天啥也不干,
此时最大利润,就是前一天的最大利润
 😾.在i-1天处于g状态,过了一天啥也不干,此时最大利润,就是前一天的最大利润
 😾.在i-1天处于g状态,第二天买了新股票,处于f状态,需要减去p[i],买东西要花钱,我们算的是利润
 😾.在i-1天处于f状态,第二天卖出股票,处于g状态,买了就会有钱,但是这是在上一次交易到这一次交易获得的钱,需要用(j-1)次交易时的最大利润进行计算

f[i][j]=max(f[i-1][j],g[i-1[j]-p[i])
g[i][j]=max(g[i-1][j],f[i-1][j-1]+p[i])

3.初始化

g根据状态转移方程我们发现,我们需要初始化f第一行,g第一行和f的第一列。

我们能不能不对f第一列进行处理,只处理那两个呢??
通过修改状态转移方程实现:
g[i][j]=max(g[i-1][j],f[i-1][j-1]+p[i])
 😾.当j>=1时,这个式子不会越界
 😾.当j= =0时,会产生越界,那我们单独处理j==0的情况不就行了。

if(j==0) g[i][j]=g[i-1][j]
else g[i][j]=max(g[i-1][j],f[i-1][j-1]+p[i])

f和g表第一行初始化为神魔呢??

 😾.首先看一下f表
  🐯.f[0][0]:第一天有股票,那么肯定就购买了,就花钱了,所以f[0][0]=-p[0];
  🐯.第一行其他位置初始化为0可不可以呢??
    如果为零,g[i][j]=max(g[i-1][j],f[i-1][j-1]+p[i])。如果g[0][1]大于f[0][0]+p[i]就会对这个式子就会产生影响,所以这里值不存在,我们在求的时候不要干扰max,那我们设置一个最小值(INT_MIN)不就行。
    如果是INT_MIN,对于这个表达式f[i][j]=max(f[i-1][j],g[i-1[j]-p[i]),最小值再减去一个值,就会发生越界。我们最好初始化为-0x3f3f3f3f(负整数的一半)。
在这里插入图片描述

emsp;😾.再看一下g表
  🐯.g[0][0]:第一天无股票,啥也没干,g[0][0]=0;
  🐯.对于其他位置,同上面f分析方式一致,初始化为-3f3f3f3f,这个也可以初始化为0,只要不影响就可以
在这里插入图片描述
如果是交易k笔次数:
若交易次数比(n/2)还大,那么剩下的就没有意义,需要对k进行缩小,因此我们可以将 k 取较小值之后再进行动态规划。

4.填表顺序

从上到下,从左往右,两个表一起填

5.返回值

我们只知道处于g状态,手上没有股票时利润才最大
但是具体交易几次利润最大我们不清楚。
返回的最大值需要在最后一行进行查找。

代码实现

class Solution {
public:
    int maxProfit(int k, vector<int>& p) 
    {
        int n=p.size();
        //建表
        vector<vector<int>>f(n,vector<int>(k+1,-0x3f3f3f));
        vector<vector<int>>g(n,vector<int>(k+1,-0x3f3f3f));
        //初始化
        f[0][0]=-p[0];
        g[0][0]=0;
        //填表
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<=k;j++)
            {
                f[i][j]=max(f[i-1][j],g[i-1][j]-p[i]);
                g[i][j]=g[i-1][j];
                if(j-1>=0)     g[i][j]=max(g[i-1][j],f[i-1][j-1]+p[i]);
            }
        }
        int reMax=0;
        for(int m=0;m<=k;m++)
        {
            reMax=max(reMax,g[n-1][m]);
        }
        return reMax;
    }
};

总结

以上就是我们对,希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lim 鹏哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值