【動態規劃】——打家劫舍問題
今天我們來總結一下動態規劃中的打家劫舍問題
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;
}
};