【陪伴式刷题】Day 42|动态规划|“打家劫舍”专题(198、213、337)

本文通过对198.打家劫舍、213.打家劫舍II、337.打家劫舍III三道题的总结分析,一篇文章打包解决"打家劫舍"专题(本专题的刷题顺序与题解分析均参考卡哥的代码随想录)

198.打家劫舍

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入: 1,2,3,1 输出: 4 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入: 2,7,9,3,1 输出: 12 解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 400

地址

leetcode.cn/problems/ho...

解题方法

java 复制代码
class Solution {
    public int rob(int[] nums) {
        if (nums == null) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        // dp[i] 在不发动警报的前提下,从[0,i]之间的房子中能获取到最大金额数为dp[i]
        int[] dp = new int[nums.length];
        // 初始化dp数组
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[nums.length - 1];
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组长度
  • 空间复杂度:O(n),其中 n 是数组长度

213.打家劫舍II

题目描述

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入: nums = 2,3,2 输出: 3 解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入: nums = 1,2,3,1 输出: 4 解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入: nums = 1,2,3 输出: 3

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 1000

地址

leetcode.cn/problems/ho...

解题方法

java 复制代码
class Solution {
    public int rob(int[] nums) {
        if (nums == null) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        int result1 = rob1(nums, 0, nums.length - 2);
        int result2 = rob1(nums, 1, nums.length - 1);
        return Math.max(result1, result2);
    }

    public int rob1(int[] nums, int startIndex, int endIndex) {
        if (nums == null) {
            return 0;
        }
        if (endIndex == startIndex) {
            return nums[endIndex];
        }
        // dp[i] 在不发动警报的前提下,从[0,i]之间的房子中能获取到最大金额数为dp[i]
        int[] dp = new int[nums.length];
        // 初始化dp数组
        dp[startIndex] = nums[startIndex];
        dp[startIndex+1] = Math.max(nums[startIndex], nums[startIndex + 1]);
        for (int i = startIndex + 2; i < endIndex + 1; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[endIndex];
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是数组长度

  • 空间复杂度:O(n),其中 n 是数组长度

337.打家劫舍III

题目描述

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为root。

除了root之外,每栋房子有且只有一个"父"房子与之相连。一番侦察之后,聪明的小偷意识到"这个地方的所有房屋的排列类似于一棵二叉树"。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的root。返回在不触动警报的情况下 ,小偷能够盗取的最高金额。

示例 1:

输入: root = 3,2,3,null,3,null,1 输出: 7 解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:

输入: root = 3,4,5,1,3,null,1 输出: 9 解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

提示:

  • 树的节点数在[1, 10^4] 范围内
  • 0 <= Node.val <= 10^4

中文版地址

leetcode.cn/problems/ho...

解题方法

java 复制代码
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
        //dp[0] 不偷当前节点得到的金额;dp[1] 偷当前节点得到的金额
        if (root == null) {
            return 0;
        }
        int[] dp = traveral(root);
        return Math.max(dp[0], dp[1]);
    }

    private int[] traveral(TreeNode cur) {
        int[] dp = {0, 0};
        if (cur == null) {
            return dp;
        }
        int[] leftResult = traveral(cur.left);
        int[] rightResult = traveral(cur.right);
        dp[0] = Math.max(leftResult[0], leftResult[1]) + Math.max(rightResult[0], rightResult[1]);
        dp[1] = leftResult[0] + rightResult[0] + cur.val;
        return dp;
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是二叉树的节点数

  • 空间复杂度:O(n),其中 n 是二叉树的节点数,主要是递归栈占用的空间,最坏情况:二叉树为线性结构时O(n),最优情况:二叉树为平衡树时为O(logn)

总结

我们来对比下这三道题:

首先它们背景相同,都要求相邻的房屋不能同时被偷盗,计算不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

其次就是差异点:198是数组,213是环,337是二叉树,在做这三道题时我是有种越来越难的感觉(不知道大部分小伙伴是不是跟我一样=_=),其实主要就是数据结构使得考虑的情形越来越复杂,于是题目就越来越难。

其实,198是一道很典型的动态规划问题,即取前一状态的最优解,如果做完198再去做213会觉得213的方法很巧妙,213的关键在于当数组变为环以后,第一个值跟最后一个值就变成了相邻的,于是不能同时取,那么取哪个呢?

自然是取哪个大就取哪个,于是就可以将这一个问题拆成两个问题:

  1. 取第一个元素不取最后一个元素时,能够偷窃到的最高金额
  2. 不取第一个元素取最后一个元素时,能够偷窃到的最高金额

这就把复杂的环分解成了两个简单的普通数组,于是我们就可以通过执行两次198分别求出这两个数组的能够偷窃到的最高金额,然后取最大值。

而337则需要熟悉二叉树的遍历了,这里比较难想到的是利用一个dp[0]来表示不取当前节点的最大金额和利用dp[1]表示取当前节点的最大金额(我也是看了卡哥的解析才明白的),又利用递归栈分别可以存局部变量的特点,迭代后序遍历二叉树(因为需要通过子节点的金额推断父节点的金额,所以徐需要后序遍历),每次获取当前二叉树的最大金额,最后推到根节点。

相关推荐
Baron X13 分钟前
Eclipse Temurin 21 (JRE) Alpine 镜像获取、离线导入与推送操作
java·docker·eclipse·temurin
孙浩君26 分钟前
我用 Spring Boot + xterm.js 写了个终端风格互动叙事游戏引擎:游戏即文件,上传一个 JSON 就能玩
java
jose34 分钟前
URL 里多写了一个端口,为什么请求还能调通?
java·后端
特立独行的猫a36 分钟前
仓颉语言UI开发实战:我用仓颉写了个桌面版力扣
算法·leetcode·ui·tauri·仓颉·cj-tarui
孙浩君38 分钟前
我用 Spring Boot 写了个「终端风格模拟经营游戏引擎」:引擎 + JSON 游戏文件,开箱即玩
java
y1su1 小时前
Leetcode 动态规划 模板
java·数据结构·算法·leetcode·动态规划
Wang's Blog1 小时前
Java 项目实战: 交友项目 - JWT 概述:为什么要用 Token 换掉 Session
java·ui·交友
SL_staff2 小时前
低代码选型避坑指南:为什么‘能拖拽’不等于‘能交付’?——基于JVS八大引擎的四维交付能力评估框架
java·低代码·spring cloud
All for pursuit.2 小时前
【位运算-3】136.只出现一次的数字
数据结构·c++·算法·leetcode
李福春2 小时前
技术思考问题6:技术演讲该晒成功架构,还是交代失败与取舍?
java·开发语言·架构·computer use·深圳同盟·腾讯云架构师技术同盟·反演思维