leetcode:740. 删除并获得点数

该博客详细解析了LeetCode 740题目的解决方案,通过动态规划策略来实现最大收益。文章介绍了问题背景,明确了算法思路,包括如何处理5元钱的特殊情况,以及如何构建状态转移方程。通过排序和去重,确保了相邻房屋的处理正确性。最后给出了完整的C++代码实现。
摘要由CSDN通过智能技术生成

题目来源

题目描述

在这里插入图片描述

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

在这里插入图片描述
怎么偷:

在这里插入图片描述

算法

类比下打家劫舍,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];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值