动态规划<三>多状态dp问题

目录

1.第一题

打家劫舍问题

 1.第二题

 2.第三题

3.第四题

5.第五题

买股票问题

1.第六题

 2.第七题

 3.第八题

4.第九题 


1.第一题

LeetCode<面试题17.16> 按摩师

画图分析:

使用动态规划解决问题

1.确定状态表示   根据经验+题目要求

dp[i]表示:选择到i位置时,最长的预约时长

但这里其实还可以再划分这个状态表示,选择到i位置时,对于i位置有选与不选两种情况

f[i]:表示选择到i位置时,i位置值(nums[i])必选,最长的预约时长

g[i]:表示选择到i位置时,nums[i]不选,最长的预约时长

2.状态转移方程

3.初始化

根据状态转移方程,发生越界的是f[0],g[0]

根据状态表示,f[0]表示选择到0位置,且nums[0]必选的最长时长  f[0]=nums[0]

同理,g[0]=0

4.填表顺序    从左往右两个表一起填

5.返回值

对于最后一个位置(n-1)有选与不选两种情况,即返回max(f[n-1],g[n-1])

具体代码

int massage(vector<int>& nums) 
    {
        int n=nums.size();
        if(n==0) return 0;//处理边界

        vector<int> f(n),g(n);
        f[0]=nums[0];
        for(int i=1;i<n;++i)
        {
            f[i]=g[i-1]+nums[i];
            g[i]=max(f[i-1],g[i-1]);
        }

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

打家劫舍问题

 1.第二题

OJ传送门 LeetCode<198> 打家劫舍

画图分析

 使用动态规划来解决

1.确定状态表示

dp[i]:表示当偷到i位置的房屋时,此时的偷到的最高金额

但同样的对于i位置的处理,可以有偷与不偷两种情况

f[i]:表示偷到i位置时,偷i位置(nums[i]),此时的最高金额

g[i]:表示偷到i位置时,不偷nums[i],此时的最高金额

2.状态转移方程

3.初始化

根据状态表示,f[0]=nums[0],g[0]=0

4.填表顺序  从左往右两个表一起填

5.返回值   

对于最后一个位置(n-1)有选与不选两种情况,即返回max(f[n-1],g[n-1])

具体代码

int rob(vector<int>& nums) 
    {
        //1.创建dp表
        //2.初始化
        //3.填表
        //4.返回值
        int n=nums.size();
        vector<int> f(n),g(n);
        f[0]=nums[0];
        for(int i=1;i<n;++i)
        {
            f[i]=g[i-1]+nums[i];
            g[i]=max(f[i-1],g[i-1]);
        }
        return max(f[n-1],g[n-1]);
    }

 2.第三题

OJ传送门 LeetCode<213> 打家劫舍II

画图分析

 对于这个问题,我们会发现对于上题(打家劫舍)明显不同的一点为第一个位置的区别

本题第一个位置偷后,最后一个位置绝对不能偷,

而对于上题最后一个位置可以偷也可以不偷两种情况

优化:

这里对于这点不同,我们可以单独拿出来研究一下

 对于剩下的状态表示,状态转移方程等参考上题

具体代码

int rob(vector<int>& nums) 
    {
        int n=nums.size();
        return max(nums[0]+rob1(nums,2,n-2),rob1(nums,1,n-1));
    }
    int rob1(vector<int>& nums,int left,int right)
    {
        int n=nums.size();
        //注意数据个数范围
        if(left>right) return 0;
        vector<int> f(n),g(n);
        f[left]=nums[left];//因为开始位置不一定是0,初始化时需要注意
        for(int i=left+1;i<=right;++i)
        {
            f[i]=g[i-1]+nums[i];
            g[i]=max(f[i-1],g[i-1]);
        }
        return max(f[right],g[right]);//注意返回的是最后一个位置,这里是right
    }

3.第四题

OJ传送门 LeetCode<740> 删除并获得点数

画图分析

对于遇到新问题时,我们可以尽量把新问题往我们熟悉的问题上进行转化

 因此通过此处的预处理操作就可将此问题转化为"打家劫舍问题",对于动态规划的步骤参考上面

具体代码

int deleteAndEarn(vector<int>& nums) 
    {
        //1.统计数组中每个数据的总和
        //对于这里所要开的空间,可以参考数据范围[1,10000]
        const int N=10001;
        int count[N]={0};
        for(auto x:nums)  count[x]+=x;
        //2.做一次打家劫舍问题
    
        vector<int> f(N),g(N);
        for(int i=1;i<N;++i)
        {
            f[i]=g[i-1]+count[i];
            g[i]=max(f[i-1],g[i-1]);
        }
        return max(f[N-1],g[N-1]);
    }

5.第五题

OJ传送门 LeetCode<LCR 091> 粉刷房子

画图分析

 使用动态规划解决

1.状态表示

dp[i]表示粉刷到i号房子时的最少花费

而对于i号房子有刷红蓝绿三种情况,因此还可以再次划分状态表示,且使用二维的形式

dp[i][0]:粉刷到i号房子时,且i号房子使用红色粉刷时的最少花费

dp[i][1]:粉刷到i号房子时,且i号房子使用蓝色粉刷时的最少花费

dp[i][2]:粉刷到i号房子时,且i号房子使用绿色粉刷时的最少花费

2.状态转移方程

 3.初始化

这里有三种的情况的话,就要初始化三个值的,分别为dp[0][0],dp[0][1],dp[0][2]

为了简化初始化过程,可以采用添加一个虚拟节点的

这是就需要关注虚拟节点中的值,要保证后续填表是正确的

还需要注意使用原数组时的下标映射关系

4.初始化 从左往右填三个表

5.返回值  因为最后一个房子有三种粉刷情况 min(dp[n][0],min(dp[n][1],dp[n][2]))

具体代码

int minCost(vector<vector<int>>& costs) 
    {
        int n=costs.size();
        vector<vector<int>> dp(n+1,vector<int>(3));
        for(int i=1;i<=n;++i)
        {
            dp[i][0]=min(dp[i-1][1],dp[i-1][2])+costs[i-1][0];
            dp[i][1]=min(dp[i-1][0],dp[i-1][2])+costs[i-1][1];
            dp[i][2]=min(dp[i-1][0],dp[i-1][1])+costs[i-1][2];
        }
        return min(dp[n][0],min(dp[n][1],dp[n][2]));
    }

买股票问题

1.第六题

OJ传送门 LeetCode<309> 买卖股票的最佳时机含冷冻期

画图分析 

使用动态规划解决

1.确定状态表示

dp[i]表示到第i天时,获得的最高利润

但对于第i天具体所做的操作可以有买股票,卖出股票,冷冻期不做任何事,分为这三种

这里我们可以划分为三种状态的"买入","冷冻期(卖出股票后处于的状态)","可交易(手里没有股票,且没有处于冷静期)",为了方便表示可以采用n*3二维数组的形式表示

dp[i][0]:表示第i天结束后,处于买入状态时的最高利润

dp[i][1]:表示第i天结束后,处于可交易状态时的最高利润

dp[i][2]:表示第i天结束后,处于冷冻状态时的最高利润

2.状态转移方程

对于多状态之间可以相互转化的,可以采用状态机的方式进行分析

根据有效的箭头列出状态转移方程

根据箭头的数量来计算,统计每个状态的每一个箭头,dp[i-1][箭尾状态标识]+所做的操作

dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i])

dp[i][1]=max(dp[i-1][1],dp[i-1][2])

dp[i][2]=dp[i-1][0]+prices[i]

 3.初始化

根据状态转移方程,需要初始化的为dp[0][0],dp[0][1],dp[0][2]

根据状态表示

dp[0][0]表示第0天结束时,处于买入状态的最高利润

应该是第0天时买当天的股票,此时利润为-prices[0]

dp[0][1]表示第0天结束时,处于可交易状态的最高利润

应该是第0天时啥也没做,此时利润为0

dp[0][2]表示第0天结束时,处于冷冻状态的最高利润

应该是第0天时买当天的股票,又当天卖出了,此时利润为0

4.填表顺序

从左往右直接填三个表

5.返回值

当到最后一天结束时,是有三种状态的,每种状态对应一个最高利润,结果应该为三者取大

max(dp[n-1][0],dp[n-1][1],dp[n-1][2])

具体代码

int maxProfit(vector<int>& prices) 
    {
        int n=prices.size();
        vector<vector<int>> dp(n,vector<int>(3));
        dp[0][0]=-prices[0];
        for(int i=1;i<n;++i)
        {
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][2]);
            dp[i][2]=dp[i-1][0]+prices[i];
        }
        return max(dp[n-1][0],max(dp[n-1][1],dp[n-1][2]));
    }

 2.第七题

OJ传送门 LeetCode<714>买卖股票的最佳时机含手续费

画图分析

 使用动态规划解决

1.确定状态转移

dp[i]表示第i天结束后,所获得的最高利润

而对于本题第i+1天所处的状态有,买了股票的"买入状态",卖出股票的"可交易状态"

这里再对状态表示进行划分,可以采用N*2二维的表示,也可以采用两个函数f[i],g[i]表示

f[i]表示第i天结束之后,所处的为买入状态时的最高利润

g[i]表示第i天结束之后,所处的为可交易状态时的最高利润

2.根据状态机求状态转移方程

3.初始化

根据状态转移和状态转移方程

f[0]初始化为-prices[0],g[0]=0

4.填表顺序 从左往右一次填两个表

5.返回值  max(f[n-1],g[n-1]) 这里注意是当最后一天结束时处于是买入的话,一定不是最高利润,可以直接返回 g[n-1]

具体代码

 int maxProfit(vector<int>& prices, int fee) 
    {
        int n=prices.size();
        vector<int> f(n),g(n);
        f[0]=-prices[0];
        for(int i=1;i<n;++i)
        {
            f[i]=max(f[i-1],g[i-1]-prices[i]);
            g[i]=max(g[i-1],f[i-1]+prices[i]-fee);
        }
        return g[n-1];
    }

 3.第八题

OJ 传送门 LeetCode<123>买卖股票的最佳时机 III

画图分析

使用动态规划来解决

1.确定状态表示

dp[i]表示第i天结束时,获得的最高利润

但同样的对于第i天结束时所处的状态有买了股票的"买入状态"和卖出股票的“可交易状态”

对上述的状态表示再进行细分,采用两个函数f[i],g[i]表示,但对于本题还有个限制条件就是交易次数不超过两次,因此对买入和可交易状态还可以进行再细分,有当第i天结束时,完成0/1/2次交易的最高利润,这样的话就会有很多的状态

但为了减少状态表示的话,就可以再增加一维,最终的状态表示为

f[i][j]:表示当第i天结束后,完成了j次交易,且处于买入状态,此时的最高利润

g[i][j]:表示当第i天结束后,完成了j次交易,且处于可交易状态,此时的最高利润

2.状态转移方程

3.初始化

对于f[i][j]的状态转移方程,只需要初始化两个表的第一行就行

对于g[i][j]的状态转移方程,由于有f[i-1][j-1],所以需要初始化f表第一行和第一列

为了处理上述的初始化情况,我们可以通过调整状态转移方程来转化为只初始化第一行即可

4.填表顺序 

从上往下填写每一行,每一行从左往右

5.返回值

返回g表最后一行的最大值 

具体代码

 const int FIM=0x3f3f3f3f;
    int maxProfit(vector<int>& prices) 
    {
        int n=prices.size();
        vector<vector<int>> f(n,vector<int>(3,-FIM));
        auto g=f;
        f[0][0]=-prices[0];
        g[0][0]=0;

        for(int i=1;i<n;++i)
        {
            for(int j=0;j<3;++j)
            {
                f[i][j]=max(f[i-1][j],g[i-1][j]-prices[i]);
                g[i][j]=g[i-1][j];
                if(j>=1)
                g[i][j]=max(g[i][j],f[i-1][j-1]+prices[i]);
            }
        }
        int ret=0;
        for(int j=0;j<3;++j)
        {
            ret=max(ret,g[n-1][j]);
        }
        return ret;
    }

4.第九题 

OJ传送门 LeetCode<188>买卖股票的最佳时机IV

画图分析

其余的如状态表示,状态转移方程,填表顺序,返回值和上题一样

这里说一下初始化时的不同,因这里的k可能大于数据个数,这样在初始化时会浪费空间的,可以这样处理 k=min(k,n/2),两天交易一次的

在处理第一行时,f[0][0]依然是-p[0][0],g[0][0]=0,其余第一行的位置都填0x3f3f3f3f

具体代码

const int FIN=0x3f3f3f3f;
    int maxProfit(int k, vector<int>& prices) 
    {
        int n=prices.size();
        k=min(k,n/2);//预处理
        vector<vector<int>> f(n,vector<int>(k+1,-FIN));
        auto g=f;
        f[0][0]=-prices[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]-prices[i]);
                g[i][j]=g[i-1][j];
                if(j>=1)//若状态存在
                g[i][j]=max(g[i][j],f[i-1][j-1]+prices[i]);
            }
        }
        int ret=0;
        for(int j=0;j<=k;++j)
        {
            ret=max(g[n-1][j],ret);
        }
        return ret;
    }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值