【动态规划】——打家劫舍问题

今天我们来总结一下动态规划中的打家劫舍问题

1、打家劫舍问题

分析:设dp[i]表示打劫到第i座房屋时所抢劫到的总金额。此时有两种情况:我们不能打劫第i家房屋,因为我们已经打劫了第i-1家房屋。此时dp[i]=dp[i-1]。第二种情况,我们没有打劫第i-1家,所以我们可以打劫第i家。此时dp[i]=dp[i-2]+nums[i-1](因为我们没有打劫第i-1家房屋,所以我们已经获得的最大收益是dp[i-2])。则状态转移方程为:

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

class Solution {
public:
    int rob(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n+1,0);
        dp[1]=nums[0];
        for(int i=2;i<=n;i++){
            dp[i]=max(dp[i-2]+nums[i-1],dp[i-1]);
        }
        return dp[n];
    }
};

2、打家劫舍问题II

Example 1:

Input: nums = [2,3,2]
Output: 3
Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2), because they are adjacent houses.
Example 2:

Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 3:

Input: nums = [1,2,3]
Output: 3
 

Constraints:

1 <= nums.length <= 100
0 <= nums[i] <= 1000

分析:此题与正常打家劫舍问题唯一的区别在于所给数组为环形,即首尾是相接的。首先考虑n>3的情况:由于是环形数组,所以要想获得最大收益首尾元素只能取其一。因此我们只需要用到两个数组来分别dp去掉首元素和去掉尾元素的情况,再取两者中的较大值即可。

n<=3时,我们只需要返回数组中的最大值就行了。因为当数组长度小于3时,我们只能抢劫一家,自然挑价值最大的下手。

class Solution {
public:
    int rob(vector<int>& nums) {
        int n=nums.size();
        if(n==1) return nums[0];
        if(n==2) return max(nums[0],nums[1]);
        if(n==3) return max(nums[0],max(nums[1],nums[2]));

        vector<int> leftDp(n+1,0);
        vector<int> rightDp(n+1,0);
        leftDp[1]=nums[0];

        for(int i=2;i<n;i++){
            leftDp[i]=max(leftDp[i-1],leftDp[i-2]+nums[i-1]);
        }
        leftDp[n]=leftDp[n-1];

        for(int i=2;i<=n;i++){
            rightDp[i]=max(rightDp[i-1],rightDp[i-2]+nums[i-1]);
        }

        return max(leftDp[n],rightDp[n]);
    }
};

变式题:删除与获得点数(题自力扣)

You are given an integer array nums. You want to maximize the number of points you get by performing the following operation any number of times:

Pick any nums[i] and delete it to earn nums[i] points. Afterwards, you must delete every element equal to nums[i] - 1 and every element equal to nums[i] + 1.
Return the maximum number of points you can earn by applying the above operation some number of times.

 

Example 1:

Input: nums = [3,4,2]
Output: 6
Explanation: You can perform the following operations:
- Delete 4 to earn 4 points. Consequently, 3 is also deleted. nums = [2].
- Delete 2 to earn 2 points. nums = [].
You earn a total of 6 points.
Example 2:

Input: nums = [2,2,3,3,3,4]
Output: 9
Explanation: You can perform the following operations:
- Delete a 3 to earn 3 points. All 2's and 4's are also deleted. nums = [3,3].
- Delete a 3 again to earn 3 points. nums = [3].
- Delete a 3 once more to earn 3 points. nums = [].
You earn a total of 9 points.
 

Constraints:

1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104

这道题为打家劫舍I的变式。题中要求是取了nums[i]可以赚取相应的点数,但是值为nums[i]-1和nums[i]+1的点数我们就无法赚取。我们可以得到当选取了nums[i]后,我们需要将nums中所有的nums[i]全部取出,以尽可能多地获取点数。

因此我们需要统计nums中各元素出现的频率,可以使用hash技术,但此处需要有序,所以我们使用C++中的map容器。然后将其中的键值存入一个数组vec中,对该数组进行dp,方法与打家劫舍问题类似,即可获得答案。

class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        int n=nums.size();
        map<int,int> freq;
        for(int i=0;i<n;i++) freq[nums[i]]++;
        vector<int> vec;
        for(auto it=freq.begin();it!=freq.end();it++) vec.push_back(it->first);

        int size=vec.size();
        vector<int> dp(size,0);
        dp[0]=vec[0]*freq[vec[0]];
        int res=dp[0];
        for(int i=1;i<size;i++){
            if(vec[i]==vec[i-1]+1){
                dp[i]=max(dp[i-1],(i-2>=0?dp[i-2]:0)+vec[i]*freq[vec[i]]);//
            }
            else{
                dp[i]=dp[i-1]+vec[i]*freq[vec[i]];
            }
            res=max(res,dp[i]);
        }
        return res;
    }
};

 

posted @ 2022-03-02 22:28  天涯海角寻天涯  阅读(421)  评论(0)    收藏  举报