Day40 力扣动态规划 :198.打家劫舍 |213.打家劫舍II | 337.打家劫舍III

Day40 力扣动态规划 :198.打家劫舍 |213.打家劫舍II | 337.打家劫舍III

  • 198.打家劫舍
    • 第一印象
    • 看完题解的思路
    • 实现中的困难
    • 感悟
    • 代码
  • 213.打家劫舍II
    • 第一印象
    • 看完题解的思路
    • 实现中的困难
    • 感悟
    • 代码
  • 337.打家劫舍III
    • 第一印象
    • 看完题解的思路
    • 实现中的困难
    • 感悟
    • 代码

今天就是打家劫舍的一天,这个系列不算难,大家可以一口气拿下。

198.打家劫舍

视频讲解:https://www.bilibili.com/video/BV1Te411N7SX
https://programmercarl.com/0198.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.html

第一印象

直接看题解学习新算法

看完题解的思路

这道题其实不难,和爬楼梯很像,就是dp算法基本的做法:先写几个,找找规律,就做出来了

最主要的是dp数组的含义:考虑到下标为 i 的那户,能偷的最大金额是 dp[i] 元 (考虑到第3家,不代表一定偷第三家,比如最后返回的事dp[length - 1],不代表一定偷了最后一家)

实现中的困难

注意nums如果只有一个元素的话,初始化就回数组越界了。

感悟

动笔算,不要懒

代码

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) return nums[0];
        
        int[] dp = new int[nums.length];

        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);

        for (int i = 2; i < dp.length; i++) {
            dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[dp.length - 1];
    }
}

213.打家劫舍II

视频讲解:https://www.bilibili.com/video/BV1oM411B7xq
https://programmercarl.com/0213.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DII.html

第一印象

对比起抢劫1,就是成环了。那么偷了第一家就不能偷最后一家,感觉影响初始化的。

那么我觉得就是分两种,一个是抢了第一家,就不能抢最后一家。另一个是 抢了第二家,最后一家抢不抢看情况。最后两个数组取最大的。

虽然方法比较笨,但是做对了,看看题解有没有精妙的。

看完题解的思路

和我一样,我是天才

实现中的困难

感悟

真是天才

代码

我的笨方法。

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) return nums[0];
        int[] dpOne = new int[nums.length];
        int[] dpLast = new int[nums.length];

        //抢第一家,不抢第二家,且不抢最后一家
        dpOne[0] = nums[0];
        dpOne[1] = dpOne[0];
        //不抢第一家,抢第二家,最后一家递推
        dpLast[0] = 0;
        dpLast[1] = nums[1];

        for (int i = 2; i < dpOne.length - 1; i++) {
            dpOne[i] = Math.max(dpOne[i - 2] + nums[i], dpOne[i - 1]);
        }
        dpOne[dpOne.length - 1] = dpOne[dpOne.length - 2];

        for (int i = 2; i < dpLast.length; i++) {
            dpLast[i] = Math.max(dpLast[i - 2] + nums[i], dpLast[i - 1]);
        }

        return dpOne[dpOne.length - 1] > dpLast[dpLast.length - 1] ? dpOne[dpOne.length - 1] : dpLast[dpLast.length - 1];

    }
}

337.打家劫舍III

视频讲解:https://www.bilibili.com/video/BV1H24y1Q7sY
https://programmercarl.com/0337.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DIII.html

第一印象

就是不能同时打劫父子节点,但也不是一层一层来。要看最大的

我试试

失败了,树形的我找不到dp数组和递推公式

看完题解的思路

悟了,这道题挺复杂的,又要dp又要递归,但我觉得核心其实是递归。

递归的思路就是对于一个节点来说,它有两个可能,偷或者不偷。

  • 偷的话,它的价值就是自己 + 左孩子不偷的价值 + 右孩子不偷的价值
  • 不偷的话,它的价值就是左孩子偷或者不偷更大的那个价值 + 右孩子偷或者不偷更大的价值

看到这,也该知道是后序遍历才对,因为要根据左右孩子的价值来算自己的。

递归的问题基本就解决了。

那么dp数组在哪呢?如果保存左孩子不偷的价值和偷的价值,就是dp数组。这个数组大小是2,dp[0] 代表不偷这个节点的最大价值,dp[1]代表偷这个节点的最大价值。

那么dp数组怎么记录每个节点的呢?把它当做递归的返回值,他在一层层递归的时候,返回给父节点用于计算。

所以递归三部曲

  • 返回值和返回值:返回值是dp数组,参数是节点
  • 终止条件:节点为null,就返回 「0, 0」这样的数组
  • 单层递归逻辑:就是上面说的递归思路。

我自己写写试试

做对了

实现中的困难

理解思路之后就没有实现困难了

感悟

关键是要讨论当前节点抢还是不抢,也就是所谓的状态。dp题目感觉首先要抓住状态,然后看这个状态怎么变化的,找到那个状态转移公式,也就是递推公式。

这道题目算是树形dp的入门题目

代码

class Solution {
    public int rob(TreeNode root) {
        int[] dp = robTree(root);
        return Math.max(dp[0], dp[1]);

    }

    private int[] robTree(TreeNode node) {
        if (node == null) {
            int[] array = {0, 0};
            return array;
        }
        //单层递归逻辑
        int[] leftdp = robTree(node.left);
        int[] rightdp = robTree(node.right);
        //根据左右节点算出自己
        int curSteal = leftdp[0] + rightdp[0] + node.val;
        int curNotSteal = Math.max(leftdp[0], leftdp[1]) + Math.max(rightdp[0], rightdp[1]);
        //不偷自己dp[0]偷自己dp[1]
        int[] dp = {curNotSteal, curSteal};
        return dp;
    }
}

你可能感兴趣的:(leetcode,动态规划,算法)