动态规划--Day04--打家劫舍--740. 删除并获得点数,3186. 施咒的最大总伤害,2140. 解决智力问题

动态规划–Day04–打家劫舍–740. 删除并获得点数,3186. 施咒的最大总伤害,2140. 解决智力问题

今天要训练的题目类型是:【打家劫舍】,题单来自@灵艾山茶府

掌握动态规划(DP)是没有捷径的,咱们唯一能做的,就是投入时间猛猛刷题。

动态规划要至少刷100道才算入门!

记忆化搜索是新手村神器。方便理解,写完之后可以转译成递推。

但是有些题目只能写递推,才能优化时间复杂度。熟练之后直接写递推也可以。

740. 删除并获得点数

思路【我】:

终于能自己做出来一题打家劫舍了!!!

  1. 首先,使用map记录每种元素与对应的总和:map<元素,元素总和>,比如nums里面是[3,5,5]那么,map[5] = 10;
  2. 考虑选i,那么i-1不能选,但是怎么把i+1的数删去?
  3. 那就直接站在i-1的角度思考问题。
    • 情况一:如果i-1要选,那么i-2不能选,i不能选
    • 情况二:要么i-1不选,那么i-2可以选,i可以选
    • 取两者的较大值
class Solution {
    public int deleteAndEarn(int[] nums) {
        int n = nums.length;
        int[] map = new int[10004];
        // map<元素,元素总和>,比如nums里面是[3,5,5]那么,map[5] = 10;
        for (int i = 0; i < n; i++) {
            map[nums[i]] += nums[i];
        }
        int[] f = new int[10004];
        f[1] = map[1];
        for (int i = 2; i < map.length; i++) {
            // 选i,那么i-1不能选,但是怎么把i+1的数删去?
            // 那就直接站在i-1的角度思考问题。
            // 考虑i-1,如果i-1要选,那么i-2不能选,i不能选
            // 要么i-1不选,那么i-2可以选,i可以选
            int res1 = f[i - 1];
            int res2 = f[i - 2] + map[i];
            f[i] = Math.max(res1, res2);
        }
        return f[f.length - 1];
    }
}

上面是一刷时候的题解。发现计算f[i]的时候并不会影响map[i],那么可以直接在map[i]上面计算,节省空间。

class Solution {
    public int deleteAndEarn(int[] nums) {
        int n = nums.length;
        int[] map = new int[10004];
        for (int i = 0; i < n; i++) {
            map[nums[i]] += nums[i];
        }
        for (int i = 2; i < map.length; i++) {
            int res1 = map[i - 1];
            int res2 = map[i - 2] + map[i];
            map[i] = Math.max(res1, res2);
        }
        return map[map.length - 1];
    }
}

思路:打家劫舍

后来仔细看了一下,情况一是选i-1,情况二是选i和i-2,这不就是跟198. 打家劫舍一模一样嘛。

区别就是,打家劫舍,是已经给出每家的值了,而本题要自己构建map,才是要打家劫舍的nums。

class Solution {
    public int deleteAndEarn(int[] nums) {
        int n = nums.length;
        int[] map = new int[10004];
        // map<元素,元素总和>,比如nums里面是[3,5,5]那么,map[5] = 10;
        for (int i = 0; i < n; i++) {
            map[nums[i]] += nums[i];
        }
        return rob(map);
    }

    // 198. 打家劫舍(空间优化)
    private int rob(int[] nums) {
        int f0 = 0;
        int f1 = 0;
        for (int x : nums) {
            int newF = Math.max(f1, f0 + x);
            f0 = f1;
            f1 = newF;
        }
        return f1;
    }
}

3186. 施咒的最大总伤害

思路:

因为power[i]的取值范围是1e9,所以不能直接开一个map[1e9]。只能先用HashMap记录<元素,元素总和>,再转为int[][] map 处理:map[][0]:元素,map[][1]:元素总和

  1. map[][0]:元素,map[][1]:元素总和,·按照map[][0],即元素值排序
  2. 初始化,讨论每个f[i],但是f[i]的值放到f[i+1]
    • while (map[j][0] < map[i][0] - 2) j++;,这样,出循环之后,就是满足map[j][0] >= map[i][0] -2。如果选i的话,j与它拉开了安全距离
    • 情况一:i不选,f[i] = f[i-1]
    • 情况二:i选,f[i] = f[j-1] + map[i][1];
    • 因为f[i]的值放到f[i+1],所以f的索引整体+1,map索引不变。最终的递推公式为:f[i + 1] = Math.max(f[i], f[j] + map[i][1]);
  3. f[i+1] 是「处理完 a[i] 后的最大伤害」—— 它是状态的索引,不是「元素的索引」。比如:
    • f[0]:处理 0 个元素(无元素),最大伤害 0;
    • f[1]:处理完 a[0] 后的最大伤害;
    • f[2]:处理完 a[1] 后的最大伤害;
    • f[n]:处理完 a[n-1] 后的最大伤害(最终答案)。
class Solution {
    public long maximumTotalDamage(int[] power) {
        // hmap<元素,元素总和>
        Map<Long, Long> hmap = new HashMap<>();
        for (long x : power) {
            hmap.merge(x, x, Long::sum);
        }
        int n = hmap.size();

        // map[][0]:元素,map[][1]:元素总和
        long[][] map = new long[n][2];
        int p = 0;
        for (Map.Entry<Long, Long> e : hmap.entrySet()) {
            map[p][0] = e.getKey();
            map[p][1] = e.getValue();
            p++;
        }
        // 按照map[][0],即元素值排序
        Arrays.sort(map, (a, b) -> Long.compare(a[0], b[0]));

        // 初始化,讨论每个f[i],但是f[i]的值放到f[i+1]
        long[] f = new long[n + 1];
        int j = 0;
        for (int i = 0; i < n; i++) {

            // 出循环之后,就是满足map[j][0] >= map[i][0] -2
            while (map[j][0] < map[i][0] - 2) {
                j++;
            }

            // 情况一:i不选,f[i] = f[i-1]
            // 情况二:i选,f[i] = f[j-1] + map[i][1];
            // 因为f[i]的值放到f[i+1],所以f的索引整体+1,map索引不变
            f[i + 1] = Math.max(f[i], f[j] + map[i][1]);
        }

        return f[n];
    }
}

// 而 f[i+1] 是「处理完 a[i] 后的最大伤害」—— 它是状态的索引,不是「元素的索引」。比如:
// f[0]:处理 0 个元素(无元素),最大伤害 0;
// f[1]:处理完 a[0] 后的最大伤害;
// f[2]:处理完 a[1] 后的最大伤害;
// …
// f[n]:处理完 a[n-1] 后的最大伤害(最终答案)。

真的是给一颗糖打一棒子。上道题刚做出来开心,这道题又绕晕了。

2140. 解决智力问题

方法:记忆化搜索

思路:

好耶,又自己做出来一道打家劫舍。

这道题和普通打家劫舍的区别:

  • 要从前往后搜索
  • 讨论i,如果要选i的话,要计算出下一个去到的索引值next

具体步骤:

  1. 初始化记忆数组:Arrays.fill(memo, -1);
  2. 从索引0开始从前往后搜索
    1. 如果索引越界,返回0
    2. 有记忆(缓存),返回缓存if (memo[i] != -1) return memo[i];
    3. 没有记忆,这个状态没有探索过
      • 情况一,不选i。(看下一个索引)dfs(i+1)
      • 情况二:选i(i能获得的分数 + 去下一个能去的索引next探索)
      • 选两种情况的较大值。写入记忆,返回。
class Solution {
    public long mostPoints(int[][] questions) {

        // 记忆化搜索。记忆数组初始值为-1,表示没有记忆(没有缓存)
        long[] memo = new long[questions.length];
        Arrays.fill(memo, -1);

        // 从0开始往后搜索
        return dfs(0, questions, memo);
    }

    private long dfs(int i, int[][] ques, long[] memo) {
        // 如果索引越界,返回0
        if (i >= ques.length) {
            return 0;
        }

        // 有记忆(缓存),返回缓存
        if (memo[i] != -1) {
            return memo[i];

        } else { // 没有记忆,这个状态没有探索过

            // 情况一,不选i。(看下一个索引)
            long res1 = dfs(i + 1, ques, memo);

            // 情况二:选i(i能获得的分数 + 去下一个能去的索引next探索)
            int next = i + ques[i][1] + 1;
            long res2 = ques[i][0] + dfs(next, ques, memo);

            // 选两种情况的较大值。写入记忆,返回。
            memo[i] = Math.max(res1, res2);
        }
        return memo[i];
    }
}

方法:动态规划(递推)

思路:

这时候f[]要申请n+1的长度,因为要用到f+1;

递归如果是从前往后的话,那么递推就要从后往前遍历了。

这时候要注意next的取值范围。

其余内容同上。所以@灵艾山茶府说,是“1:1翻译成递推”

class Solution {
    public long mostPoints(int[][] questions) {
        int n = questions.length;
        long[] f = new long[n + 1];
        for (int i = n - 1; i >= 0; i--) {

            // 情况一,不选i。(看下一个索引)
            long res1 = f[i + 1];

            // 情况二:选i(i能获得的分数 + 去下一个能去的索引next探索)
            int next = i + questions[i][1] + 1;

            // next索引不能越界
            next = next >= n ? n : next;
            long res2 = questions[i][0] + f[next];

            // 选两种情况的较大值。
            f[i] = Math.max(res1, res2);
        }
        return f[0];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值