按摩师

题目链接:按摩师

方法一:设计二维状态变量

第一步:设计状态

【状态】这个词可以理解为 【记录了求解问题到了哪一个阶段】。

由于当前这一天按摩师有两种选择:(1)接预约;(2)不接预约。但根据题意,今天是否接预约,是受到昨天影响的。为了消除这种影响,我们在状态数组要设置这个维度。

dp[i][0] 表示:区间[0,i]里预约请求序列已完成,并且下标为 i 的这一天不接受预约的情况下的最大时长
dp[i][1] 表示:区间[0,i]里预约请求序列已完成,并且下标为 i 的这一天接受预约的情况下的最大时长

说明:这个定义是有前缀性质的,即当前的状态值考虑了(或者综合了)之前的相关的状态值,第2维保存了当前最优解的决策,这种通过增加维度,消除后效性的操作在【动态规划】
问题里是非常常见的。

一般情况是,只要有约束,就可以增加一个维度消除这种约束带来的影响,再具体一点说,就是把【状态】定义的清楚、准确,【状态转移方程】就容易得到了。

第二步:状态转移方程

【状态转移方程】可以理解为 【不同阶段之间的联系】
今天只和昨天的状态相关,依然是分类讨论:

  • 今天不接受预约:

    1. 昨天不接受预约 dp[i-1][0]
    2. 昨天接受了预约 dp[i-1][1]
      取两者的最大值
      max(dp[i-1][0],dp[i-1][1]
  • 今天接受预约:(昨天没有接受预约)
    dp[i][1] = dp[i-1][0] + nums[i]

第三步:考虑初始化

从第2天开始,每天的状态值只与前一天有关,因此第一天就只好老老实实算了。好在不难判断
dp[0][0] = 0
dp[0][1] = nums[0]

第四步:考虑输出

由于状态值的定义是前缀性质,因此最后一天的状态值就考虑了之前所有的天数的情况。按摩师最后一天可以接受预约,也可以不接受预约,取两者的最大值。

第五步:考虑是否可以优化空间

由于今天只参考昨天的值,可以使用【滚动数组】完成,优化空间以后的代码丢失了一定的可读性,也会给编码增加一点点难度。

参考代码1:

class Solution {
public:
    int massage(vector<int>& nums) {
        int len = nums.size();
        if(len == 0){
            return 0;
        }
        if(len == 1){
            return nums[0];
        }
        // dp[i][0]:区间 [0, i] 里预约请求序列已选定,并且下标为 i 的这一天不接受预约的最大时长
        // dp[i][1]:区间 [0, i] 里预约请求序列已选定,并且下标为 i 的这一天接受预约的最大时长
        vector<vector<int> > dp(len,vector<int>(2));
        //初始化
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for(int i=1;i<len;i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]);
            dp[i][1] = dp[i-1][0] + nums[i];
        }
        return max(dp[len-1][0],dp[len-1][1]);
    }
};

时间复杂度:O(N),N是数组的长度
空间复杂度:O(N),状态数组的大小为2N,可以优化到常数级别。

方法二:设计一维状态变量

第一步:定义状态

dp[i]:表示区间[0,i] 里接受预约请求的最大时长

第二步:状态转移方程

这个时候因为不限定下标i 这一天是否接受预约,因此需要分类讨论:

  • 接受预约,那么昨天就一定休息。由于状态dp[i-1] 的定义涵盖了下标为 i-1 这一天接收预约的情况,状态只能从下标为 i-2 的状态转移而来:dp[i-2] + nums[i]
  • 不接收预约,不用考虑昨天是否休息,状态从下标为i-1 的状态转移过来 dp[i-1]

二者取最大值,因此状态转移方程为 dp[i] = max(dp[i-1],dp[i-2] + nums[i])。

第三步:思考初始化

看状态转移方程,下标最小到 i- 2,因此初始化的时候要把dp[0],dp[1]算出来,从dp[2]开始计算。

  • dp[0]:只有一天的时候,必须接受预约,因此dp[0] = nums[0];
  • dp[1]:头两天的时候,由于不能同时接收预约,因此最优值就是这两天的最大值 dp[1] = max(dp[0],dp[1])

第四步:思考输出

由于定义的状态有前缀性质,并且对于下标为 i 的这一天也考虑了接收预约与不接收预约的情况,因此输出就是最后一天的状态值

第五步:思考空间优化

看状态转移方程。当前状态只与前两个状态有关,我们只关心最后一天的状态值。因此依然可以使用【滚动变量】的技巧,这个时候滚动起来的就是 3 个变量了。这样的代码依然是丢失了可读性,
也存在一定编码错误的风险。
参考代码2:

class Solution {
public:
    int massage(vector<int>& nums) {
        int len = nums.size();
        if(len == 0){
            return 0;
        }else if(len == 1){
            return nums[0];
        }else if(len == 2){
            return max(nums[0],nums[1]);
        }

        vector<int> dp(len);
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);

        for(int i = 2;i<len;i++){
            dp[i] = max(dp[i-1],dp[i-2] + nums[i]);
        }
        return dp[len-1];
    }
};
posted @ 2020-10-03 11:49  focusDing  阅读(130)  评论(0编辑  收藏  举报