题目来源
题目描述
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
}
};
题目解析
思路
在偷了三处村庄后这个小偷又来到了第四个村庄进行偷窃。他发现这个村庄的居民变得聪明了,将房屋打乱了顺序,并且报警器变得更加灵敏了,在偷了5块钱之后,偷6块钱跟4块钱它都会报警,但是报警器也有"bug", 那就是偷了5块钱后继续偷5块钱报警器不会触发。
应该怎么偷呢?
- 他先将房屋重新进行了排序,写到了本子里。由于发现了报警器“bug”,对于所有为5块钱的房屋都只记录了一个,还记录了其数字以免错过。然后在他偷5块钱的时候它会将所有对应的房屋偷完
- 也就是说记事本上写了两个东西:
- 排序后的房屋金额,注意这里没有重复的(检查和前一家是否紧挨着)
- 各个房屋金额所对应的房屋数量
- 也就是说记事本上写了两个东西:
- 做完上面步骤之后就可以开始偷了,每次偷完上一个房屋时都记录上一次房屋偷了多少钱
- 在偷下一个房屋时,如果发现这次房屋金额刚好等于上一次偷窃金额+1,那就看偷当前房屋屋金额加上前前次的房屋金额大,还是上一次偷窃的房屋金额大。
dp[i] = max(dp[i-1], dp[i-2] + dp[i] * m[dp[i]]);
- 如果这一个房屋的金额比上一次偷窃的金额要比1还大时他知道不会出发报警器,因此他就将上次的钱也一起带过来了。
dp[i] = dp[i-1] + dp[i] * m[dp[i]];
- 在偷下一个房屋时,如果发现这次房屋金额刚好等于上一次偷窃金额+1,那就看偷当前房屋屋金额加上前前次的房屋金额大,还是上一次偷窃的房屋金额大。
怎么偷:
算法
类比下打家劫舍,nums[i]的值相当于门牌号(也等于这家钱的单位值),nums[i]的数量相当于这一家钱的数量,那么这家所有的钱就是num[i] * cnt
- 如果选中了nums[i],所有等于nums[i] - 1和nums[i] + 1的元素都不可以选,相当于偷了nums[i]之后,只有两家都不能偷
- 不一样的是,nums[i]可能有多个,那就意味着偷了 nums[i] 家后,要把他家的所有钱都偷了。因为偷 nums[i] 家的 代价 是左右两家都不能去偷了,这样的事实已经发生的情况下,最优解就是把 nums[i] 家偷光
定义状态:
- dp[i]为在[0, i]的范围内偷钱能够偷到的最大金额
- 那么dp[n]就是我们想要的
状态转移方程:
- 首先我们要将原数组去重后,排序,方便检测和前一家是否挨着,打家劫舍是一户挨着一户,这道题每户间存在空隙
- 同时用map记录nums[i]的数量
- 我们根据是否和前一家[是否紧挨着]分为两种情况
- 如果和前一家「没有紧挨着」,前面偷的前 + 这一家所有的前,dp[i] = dp[i - 1] + nums[i] * hashmap[nums[i]]
- 如果和前一家「紧挨着」,这家可偷可不偷,取这两种子情况的最大值
- 这家我们「偷」,那前面一家「不能去偷」,前前面偷的钱 + 这一家所有的钱,dp[i] = dp[i-2] + nums[i] * hashmap[nums[i]]
- 这家我们「不偷」,在这家时仅有前面偷的钱,dp[i] = dp[i-1]
- 注意到状态转移过程中会使用 dp[i-2] ,因此可以将 dp 数组的首位扩充一下,防止越界【常见操作】
初始化:
- dp[0] = 0 扩充的一位
- dp[1] = nums[0] * hashmap[nums[0]] 只有一家时,把这家偷光
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
vector<int> houses;
unordered_map<int, int> house_cnt;
for (int num : nums) {
if (house_cnt.count(num) == 0) {
houses.push_back(num);
}
house_cnt[num]++;
}
sort(houses.begin(), houses.end());
// for (int a : houses) cout << a << " ";
// cout << endl;
// for (auto & [a, b] : house_cnt) {cout << a << " " << b << endl;
int n = houses.size(); // 注意 n 是去重后houses长度,下面一律用houses
vector<int> dp(n + 1, 0);
dp[0] = 0;
dp[1] = houses[0] * house_cnt[houses[0]];
for (int i = 1; i < n; i++) { // i为houses的下标,dp的下标需要增加1
int cur = houses[i];
if (house_cnt.count(cur - 1) == 0) { // 和前一家「没有紧挨着」
dp[i + 1] = dp[i] + cur * house_cnt[cur];
} else { // 和前一家「紧挨着」
dp[i + 1] = max(dp[i - 1] + cur * house_cnt[cur], dp[i]);
}
}
return dp[n];
}
};