当前位置: 首页 > news >正文

【LeetCode 热题 100】198. 打家劫舍——(解法二)自底向上

Problem: 198. 打家劫舍

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(N)

整体思路

这段代码同样旨在解决 “打家劫舍” 问题,但它采用的是一种 自底向上(Bottom-Up)的动态规划 方法,也称为 迭代法制表法 (Tabulation)。这种方法通过构建一个DP表(f 数组),从最小的子问题开始,逐步计算出最终的解,避免了递归的开销。

该实现有一个巧妙之处,即通过调整DP数组的索引,使得循环内的状态转移方程无需处理边界条件,代码更为简洁。

  1. 状态定义与索引映射

    • 算法定义了一个DP数组 f。这里的 f[k] 被设计为表示“在考虑前 k-1 间房屋时能偷到的最高金额”。
    • 这是一个索引偏移的技巧。具体来说:
      • f[0]f[1] 可以看作是“哨兵”或基础情况,代表没有房屋可偷,金额为0。
      • f[2] 将代表考虑第一间房(nums[0])时的最大金额。
      • f[i+2] 将代表考虑前 i+1 间房(nums[0...i])时的最大金额。
      • 最终,f[n+1] 将代表考虑所有 n 间房(nums[0...n-1])时的最大金额。
  2. 基础情况 (Base Cases)

    • Java中 new int[n + 2] 数组默认所有元素都初始化为0。
    • f[0] = 0f[1] = 0 恰好满足我们的基础情况:在有0间或-1间房(不存在)时,最大收益为0。
  3. 状态转移

    • 算法使用一个 for 循环,从 i = 0 遍历到 n-1,这个 i 对应的是 nums 数组的索引。
    • 在循环的每一步,计算 f[i+2] 的值,它代表考虑 nums[0...i] 时的最大收益。
    • 状态转移方程是 f[i + 2] = Math.max(f[i + 1], f[i] + nums[i])
    • 我们来解析这个方程:
      • f[i+1]:根据我们的定义,它代表考虑 nums[0...i-1] 时的最大收益。这对应了不偷当前房屋 nums[i] 的情况。
      • f[i] + nums[i]f[i] 代表考虑 nums[0...i-2] 时的最大收益。这对应了当前房屋 nums[i] 的情况(因为偷了i,就不能偷i-1,所以只能从i-2的状态转移过来)。
    • 这个方程完美地表达了“偷”与“不偷”当前房屋 i 的选择。
  4. 返回结果

    • 当循环结束后,f 数组已经从 f[2] 填充到了 f[n+1]
    • 我们需要的最终答案,即考虑所有 n 间房屋的最大收益,就存储在 f[n+1] 中。

完整代码

class Solution {/*** 计算在不偷相邻房屋的情况下,能偷窃到的最高金额。* @param nums 每个房屋的金额数组* @return 最高总金额*/public int rob(int[] nums) {int n = nums.length;// f: 动态规划数组。f[k] 表示考虑前 k-1 间房屋能偷到的最大金额。// 数组大小为 n+2 是为了使用 f[0] 和 f[1] 作为哨兵,简化循环内的逻辑。int[] f = new int[n + 2];// 循环变量 i 对应 nums 数组的索引。for (int i = 0; i < n; i++) {// 状态转移方程:// 计算 f[i+2],它代表考虑房屋 nums[0...i] 时的最大收益。// 这个收益取决于对当前房屋 nums[i] 的选择:// 1. 不偷 nums[i]: 最大收益等于考虑 nums[0...i-1] 时的收益,即 f[i+1]。// 2. 偷 nums[i]:   最大收益等于 nums[i] 加上考虑 nums[0...i-2] 时的收益,即 f[i] + nums[i]。// 我们取这两者中的较大值。f[i + 2] = Math.max(f[i + 1], f[i] + nums[i]);}// 循环结束后,f[n+1] 存储的是考虑所有 n 间房屋 (nums[0...n-1]) 时的最大收益。return f[n + 1];}
}

时空复杂度

时间复杂度:O(N)

  1. 循环:算法的核心是一个 for 循环,它从 i = 0 遍历到 n-1。这个循环执行了 N 次。
  2. 循环内部操作
    • 在循环的每一次迭代中,执行的都是基本的数组访问、Math.max 和加法运算。
    • 这些操作的时间复杂度都是 O(1)

综合分析
算法由 N 次 O(1) 的操作组成。因此,总的时间复杂度是 N * O(1) = O(N)

空间复杂度:O(N)

  1. 主要存储开销:算法创建了一个名为 f 的整型数组来存储动态规划的所有中间状态。
  2. 空间大小:该数组的长度是 n + 2,其大小与输入 N 成线性关系。
  3. 其他变量n, i 等变量都只占用常数级别的空间,即 O(1)。

综合分析
算法所需的额外空间主要由 f 数组决定。因此,其空间复杂度为 O(N)

空间优化提示
观察状态转移方程 f[i+2] = Math.max(f[i+1], f[i] + nums[i]),可以发现 f[i+2] 的计算只依赖于前两个状态 f[i+1]f[i]。因此,我们不需要存储整个 f 数组,只需用两个变量来滚动记录前两个状态即可。这样可以将空间复杂度优化到 O(1)

http://www.xdnf.cn/news/1322011.html

相关文章:

  • Linux磁盘阵列
  • ChatGPT-5 对教育行业的影响与案例研究
  • OpenAL技术详解:跨平台3D音频API的设计与实践
  • C++最小生成树
  • 手写MyBatis第24弹:从单条插入到批量处理:MyBatis性能优化的关键技术
  • 手机视频怎么提取音频?3步转成MP3,超简单!
  • 决策树的笔记
  • 决策树学习报告
  • MCP协议
  • 世界模型之自动驾驶
  • 决策树:机器学习中的直观分类与回归工具
  • 【深度学习基础】PyTorch Tensor生成方式及复制方法详解
  • <数据集>遥感飞机识别数据集<目标检测>
  • 基于深度学习的车牌检测识别系统:YOLOv5实现高精度车牌定位与识别
  • Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
  • 【LLM1】大型语言模型的基本生成机制
  • 华清远见25072班C语言学习day11
  • 当使用STL容器去存放数据时,是存放对象合适,还是存放对象指针(对象地址)合适?
  • 【C++】 using声明 与 using指示
  • Linux内存管理系统性总结
  • Orange的运维学习日记--45.Ansible进阶之文件部署
  • 获粤港澳大湾区碳足迹认证:遨游智能三防手机赋能绿色通信
  • LeetCode:无重复字符的最长子串
  • 实践笔记-VSCode与IDE同步问题解决指南;程序总是进入中断服务程序。
  • LAMP 架构部署:Linux+Apache+MariaDB+PHP
  • 规避(EDR)安全检测--避免二进制文件落地
  • 云计算- KubeVirt 实操指南:VM 创建 、存储挂载、快照、VMI全流程 | 容器到虚拟机(镜像转换/资源调度)
  • 前端处理导出PDF。Vue导出pdf
  • 王树森深度强化学习DRL(三)围棋AlphaGo+蒙特卡洛
  • STRIDE威胁模型