动态规划——无后效性及如何消除后效性

无后效性

定义

无后效性是指如果在某个阶段上过程的状态已知,则从此阶段以后过程的发展变化仅与此阶段的状态有关,而与过程在此阶段以前的阶段所经历过的状态无关。利用动态规划方法求解多阶段决策过程问题,过程的状态必须具备无后效性。

简单的说,就是在计算后面的数值时,只于当前的数值有关而与之前的数值无关。

例题

leetcode-53最大子序和

[题目描述]

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

[分析]

状态的定义:以nums[i]结尾的连续子数组的最大值
状态的转移:
if(dp[i-1]>0) dp[i] = dp[i-1]+nums[i]
if(dp[i-1]<=0) dp[i] = nums[i]
for循环找到最大值

在状态的计算过程中我们可以发现,后面状态的计算只与当前状态的值有关,而与此阶段之前的值无关,所以具有无后效性

[代码实现]
int maxSubArray(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        //dp[i] 表示以nums[i]结尾的连续最大子序和
        vector<int> dp(nums.size(),0);
        dp[0] = nums[0];
        for(int i=1;i<nums.size();i++){
            if(dp[i-1] > 0)
                dp[i] = dp[i-1]+nums[i];
            else
                dp[i] = nums[i];
        }
        
        int ans = nums[0];
        //循环遍历找到最大值
        for(int i=0;i<dp.size();i++)
            ans = max(ans,dp[i]);

        return ans;
    }

有后效性

相当于在计算当前的数值时,不仅与当前状态有关,还与当前状态之前的状态的有关。

例题

leetcode-17.16按摩师

[题目描述]

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。(相当于打家劫舍)

[分析]

方法1:设计一维状态向量,进行分类讨论
状态的定义:考虑nums[0,…,i]里预约的最长时间
状态的转移:因为这个时候不限定nums[i]是否预约,所以要分情况讨论
接收预约:dp[i] = nums[i]+dp[i-1]
不接受预约:dp[i] = dp[i-1]
二者取最大值
方法2:增加一维向量,设计成二维向量,减少分类讨论
状态的定义:考虑nums[0,…,i]的最大预约时长。

dp[i][0]:区间 [0,i] 里接受预约请求,并且下标为 i 的这一天不接受预约的最大时长
dp[i][1]:区间 [0,i] 里接受预约请求,并且下标为 i 的这一天接受预约的最大时长

通过增加维度,消除后效性
状态的转移:

//dp[i][0]表示第i天不接受预约的最大时长
//max(第i-1天不接受预约的最大时长,第i-1天接受预约的最大时长)
dp[i][0] = max(dp[i-1][0],dp[i-1][1]);
//dp[i][1]表示第i天接受预约的最大时长
//第i-1天不接受预约的最大时长+nums[i]
dp[i][1] = dp[i-1][0]+nums[i];
[代码实现]

方法1

int massage(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        //dp[i]表示考虑nums[0,...i]集合中能找到的最长时间
        vector<vector<int>> dp(nums.size(),vector<int>(2,0));
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for(int i=1;i<nums.size();i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]);
            dp[i][1] = nums[i]+dp[i-1][0];
        }
        return max(dp[nums.size()-1][0],dp[nums.size()-1][1]);
    }

方法2

int massage(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        //dp[i]表示考虑nums[0,...i]集合中能找到的最长时间
        vector<int> dp(nums.size(),0);
        dp[0] = nums[0];
        for(int i=1;i<nums.size();i++)
            dp[i] = max(dp[i-1],nums[i]+(i-2>=0?dp[i-2]:0));
        return dp[nums.size()-1];
    }

leetcode-152最大乘积子数组

[题目描述]

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

[分析]

依旧采取之前状态的定义
状态的定义:以nums[i]为结尾的最大子数组的乘积
但是我们在实例中可以发现,有负数的出现,这样的话一个正数乘以负数,即最大值乘以负数就变成了最小值。因此最大值和最小值是可以相互转化的。在计算的时候,当前状态的值还有可能与之前的状态的值有关,存在后效性。可以通过消除后效性减少分类讨论的难度

[消除后效性]

方法:设计二维状态变量
状态的定义

dp[i][j]:以nums[i]为结尾的连续子数组的最值(j:0-最小值;j:1=最大值)
dp[i][0] : 以nums[i]为结尾的连续子数组的最小值
dp[i][1] : 以nums[i]为结尾的连续子数组的最大值

状态的转移

dp[i][0] = min(dp[i-1][0],nums[i]*dp[i-1][1]) if(nums[i]<0)
dp[i][1] = max(dp[i-1][1],nums[i]*dp[i-1][0]) if(nums[i]<0)

dp[i][0] = min(dp[i-1][0],nums[i]*dp[i-1][0]) if(nums[i]>=0)
dp[i][1] = max(dp[i-1][1],nums[i]*dp[i-1][1]) if(nums[i]>=0)
[代码实现]
int maxProduct(vector<int>& nums) {
        if(nums.size() == 0)
            return 0;
        //dp[i]表示以nums[i]为结尾的最小(最大)乘积子数组的乘积
        vector<vector<int>> dp(nums.size(),vector<int>(2,0));
        dp[0][0] = nums[0];
        dp[0][1] = nums[0];
        for(int i=1;i<nums.size();i++){
            if(nums[i] > 0){
                dp[i][0] = min(nums[i],nums[i]*dp[i-1][0]);
                dp[i][1] = max(nums[i],nums[i]*dp[i-1][1]);
            }
            else{
                dp[i][0] = min(nums[i],nums[i]*dp[i-1][1]);
                dp[i][1] = max(nums[i],nums[i]*dp[i-1][0]);
            }
        }
        int ans = -INT_MAX;
        for(int i=0;i<nums.size();i++){
            ans = max(dp[i][1],ans);
        }
        return ans;
    }

[参考]
1、https://leetcode-cn.com/problems/maximum-product-subarray/solution/dong-tai-gui-hua-li-jie-wu-hou-xiao-xing-by-liweiw/
2、https://leetcode-cn.com/problems/the-masseuse-lcci/solution/dong-tai-gui-hua-by-liweiwei1419-8/

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值