【打家劫舍系列】从 I 到 II : 如何将「新限制条件」拎出,将问题转化为已解决方案 |Java 刷题打卡

简介: 【打家劫舍系列】从 I 到 II : 如何将「新限制条件」拎出,将问题转化为已解决方案 |Java 刷题打卡

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 213. 打家劫舍 II ,难度为 中等


Tag : 「线性 DP」


你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。


这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。


同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。


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


示例 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 = [0]
输出:0
复制代码


提示:


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


动态规划



198. 打家劫舍 中,并没有「第一间」和「最后一间」不能同时选择的限制,因此我们从头到尾做一遍 DP 即可。


198. 打家劫舍 中,我们可以将状态定义为两维:


f[i][j]f[i][j] 代表考虑前 ii 个房间,当前 ii 房间的现在状态为 jj 的最大价值。


  • f[i][0]f[i][0] 代表考虑前 ii 个房间,并且「不选」第 ii 个房间的最大价值。由于已经明确了第 ii 个房间不选,因此 f[i][0]f[i][0] 可以直接由 max(f[i - 1][0], f[i - 1][1])max(f[i1][0],f[i1][1]) 转移而来。
  • f[i][1]f[i][1] 代表考虑前 ii 个房间,并且「选」第 ii 个房间的最大价值。由于已经明确了第 ii 个房间被选,因此 f[i][1]f[i][1] 直接由 f[i - 1][0] + nums[i]f[i1][0]+nums[i] 转移过来。


到这里,你已经解决了 198. 打家劫舍 了。


对于本题,由于只是增加了「第一间」和「最后一间」不能同时选择的限制。


通常,对于一些明显不是「增加维度」的新限制条件,我们应当考虑直接将其拎出讨论,而不是多增加一维进行状态记录。


我们可以把「第一间」&「最后一间」单独拎出来讨论:


  • 明确「不选」第一间:
  1. 初始化 f[0][0]f[0][0]f[0][1]f[0][1],均为 00
  2. 先从「第二间」开始递推到「倒数第二间」的最大价值。
  3. 再处理「最后一间」的情况:由于明确了「不选第一间」,则最后的最大价值为 max(f[n - 2][1], f[n - 2][0] + nums[n - 1])max(f[n2][1],f[n2][0]+nums[n1])
  • 允许「选」第一间:
  1. 初始化 f[0][0]f[0][0]f[0][1]f[0][1],分别为 00nums[0]nums[0]
  2. 先从「第二间」开始递推到「倒数第二间」的最大价值。
  3. 再处理「最后一间」的情况:由于明确了「选第一间」,则最后的最大价值为 max(f[n - 2][0], f[n - 2][1])max(f[n2][0],f[n2][1])


走完两遍 DP 后,再从两种情况的最大价值中再取一个 maxmax 即是答案。


代码:


class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;
        if (n == 1) return nums[0];
        // 第一间「必然不选」的情况
        int[][] f = new int[n][2];
        for (int i = 1; i < n - 1; i++) {
            f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]);
            f[i][1] = f[i - 1][0] + nums[i];
        }
        int a = Math.max(f[n - 2][1], f[n - 2][0] + nums[n - 1]);
        // 第一间「允许选」的情况
        f[0][0] = 0; f[0][1] = nums[0];
        for (int i = 1; i < n - 1; i++) {
            f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]);
            f[i][1] = f[i - 1][0] + nums[i];
        }
        int b = Math.max(f[n - 2][0], f[n - 2][1]);
        return Math.max(a, b);
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)


空间优化



不难发现,我们状态转移最多依赖到前面的 1 行,因此可以通过很机械的「滚动数组」方式将空间修改到 O(1)O(1)


代码:


class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;
        if (n == 1) return nums[0];
        // 第一间「必然不选」的情况
        int[][] f = new int[2][2];
        for (int i = 1; i < n - 1; i++) {
            f[i%2][0] = Math.max(f[(i - 1)%2][0], f[(i - 1)%2][1]);
            f[i%2][1] = f[(i - 1)%2][0] + nums[i];
        }
        int a = Math.max(f[(n - 2)%2][1], f[(n - 2)%2][0] + nums[n - 1]);
        // 第一间「允许选」的情况
        f[0][0] = 0; f[0][1] = nums[0];
        for (int i = 1; i < n - 1; i++) {
            f[i%2][0] = Math.max(f[(i - 1)%2][0], f[(i - 1)%2][1]);
            f[i%2][1] = f[(i - 1)%2][0] + nums[i];
        }
        int b = Math.max(f[(n - 2)%2][0], f[(n - 2)%2][1]);
        return Math.max(a, b);
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(1)O(1)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.213 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
1月前
|
关系型数据库 MySQL Java
【IDEA】java后台操作mysql数据库驱动常见错误解决方案
【IDEA】java后台操作mysql数据库驱动常见错误解决方案
59 0
|
5天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
17 3
|
12天前
|
Java API Apache
|
1月前
|
Java
短频快task的java解决方案
本文探讨了Java自带WorkStealingPool的缺陷,特别是在任务中断方面的不足。普通线程池在处理短频快任务时存在锁竞争问题,导致性能损耗。文章提出了一种基于任务窃取机制的优化方案,通过设计合理的窃取逻辑和减少性能损耗,实现了任务的高效执行和资源的充分利用。最后总结了不同场景下应选择的线程池类型。
|
1月前
|
小程序 Java
小程序访问java后台失败解决方案
小程序访问java后台失败解决方案
44 2
|
2月前
|
传感器 监控 数据可视化
【Java】智慧工地解决方案源码和所需关键技术
智慧工地解决方案是一种新的工程全生命周期管理理念。它通过使用各种传感器、数传终端等物联网手段获取工程施工过程信息,并上传到云平台,以保障数据安全。
80 7
|
1月前
|
存储 前端开发 Java
浅谈Java中文乱码浅析及解决方案
浅谈Java中文乱码浅析及解决方案
71 0
|
1月前
|
Java
Error:java: 无效的目标发行版: 11解决方案
Error:java: 无效的目标发行版: 11解决方案
68 0
|
1月前
|
Java Maven Spring
用Spring导致的无法运行Java文件的问题的解决方案
本文提供了解决在IntelliJ IDEA社区版中使用Spring Initializr插件创建Spring项目后,Java文件无法运行的问题的方法,主要是通过加载Maven项目来解决。
63 0
|
3月前
|
Java 开发者
在Java编程中,if-else与switch作为核心的条件控制语句,各有千秋。if-else基于条件分支,适用于复杂逻辑;而switch则擅长处理枚举或固定选项列表,提供简洁高效的解决方案
在Java编程中,if-else与switch作为核心的条件控制语句,各有千秋。if-else基于条件分支,适用于复杂逻辑;而switch则擅长处理枚举或固定选项列表,提供简洁高效的解决方案。本文通过技术综述及示例代码,剖析两者在性能上的差异。if-else具有短路特性,但条件增多时JVM会优化提升性能;switch则利用跳转表机制,在处理大量固定选项时表现出色。通过实验对比可见,switch在重复case值处理上通常更快。尽管如此,选择时还需兼顾代码的可读性和维护性。理解这些细节有助于开发者编写出既高效又优雅的Java代码。
50 2