1. 程式人生 > 其它 >【動態規劃】——打家劫舍問題

【動態規劃】——打家劫舍問題

今天我們來總結一下動態規劃中的打家劫舍問題

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;
    }
};