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

动态规划--Day03--打家劫舍--198. 打家劫舍,213. 打家劫舍 II,2320. 统计放置房子的方式数

动态规划–Day03–打家劫舍–198. 打家劫舍,213. 打家劫舍 II,2320. 统计放置房子的方式数

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

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

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

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

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

198. 打家劫舍

方法:记忆化搜索(递归)

思路:

和爬楼梯几乎是同一个模板的。爬楼梯是memo[i] = res1 + res2;,而打家劫舍是memo[i] = Math.max(res1, res2);

后来忽然发现了一个细微的差别,爬楼梯,缓存一般是开n+1位,而打家劫舍缓存一般开n位就够了。

  1. 使用memo记忆,缓存已经探索过的节点
  2. 从数组最后一个值开始往前探索
    • 递归结束判断:如果索引超出范围,则返回
    • 如果memo有值,用缓存值
    • 对于当前探索的i这个节点,能不能偷,有两个情况:
      • 情况一:偷i-1家,那么i家就不能偷(递归进去探索i-1节点)
      • 情况二:偷i-2家,那么i家也能偷(递归进去探索i-2节点)
      • 返回两种情况的较大值,顺便保存到记忆
class Solution {public int rob(int[] nums) {int n = nums.length;// 记忆(缓存),默认未使用标志为-1int[] memo = new int[n];Arrays.fill(memo, -1);// 从索引n-1开始搜索(也就是数组的最后一个值)return dfs(n - 1, nums, memo);}private int dfs(int i, int[] nums, int[] memo) {// 索引超出范围,返回if (i < 0) {return 0;}// 如果已经判断过这种情况了,直接用记忆(缓存)if (memo[i] != -1) {return memo[i];} else {// 情况一:偷i-1家,那么i家就不能偷// 情况二:偷i-2家,那么i家也能偷int res1 = dfs(i - 1, nums, memo);int res2 = dfs(i - 2, nums, memo) + nums[i];// 返回两种情况的较大值,顺便保存到记忆memo[i] = Math.max(res1, res2);}return memo[i];}
}

从dfs函数来看,一进去先判断索引是否超出范围,超出了则返回。其实是浪费多了一次资源(因为调用函数要压栈,出栈等,时间空间都变慢了)

可以改成这样:先判断索引,再考虑调用函数:

private int dfs(int i, int[] nums, int[] memo) {if (memo[i] != -1) {return memo[i];} else {// 先判断索引合法,再调用函数int res1 = 0;if (i - 1 >= 0) {res1 = dfs(i - 1, nums, memo);}// res2要赋值为nums[i]int res2 = nums[i];if (i - 2 >= 0) {res2 = dfs(i - 2, nums, memo) + nums[i];}memo[i] = Math.max(res1, res2);}return memo[i];
}

注意,这里res2要默认赋值为nums[i]。因为这是一个决策,是决定偷i-2,那么i家先偷了,再去i-2,然后发现i-2没有人住(超出索引范围),那么i家我也偷到了。

如果写成这样是错的:

private int dfs(int i, int[] nums, int[] memo) {if (memo[i] != -1) {return memo[i];} else {int res1 = 0;int res2 = 0;if (i - 1 >= 0) {res1 = dfs(i - 1, nums, memo);}if (i - 2 >= 0) {res2 = dfs(i - 2, nums, memo) + nums[i];}memo[i] = Math.max(res1, res2);}return memo[i];
}

方法:动态规划(递推)

思路:

递推要初始化,因为f[i]需要依赖f[i-1]和f[i-2],所以要初始化前两个数,还要特判n==1的情况。

class Solution {public int rob(int[] nums) {int n = nums.length;// 如果只有一家,直接偷,返回。if (n == 1) {return nums[0];}int[] f = new int[n];// 递推要初始化,因为f[i]需要依赖f[i-1]和f[i-2],所以要初始化前两个数f[0] = nums[0];f[1] = Math.max(nums[0], nums[1]);// 从第三个数开始遍历for (int i = 2; i < n; i++) {// 情况一:偷i-1家,那么i家就不能偷// 情况二:偷i-2家,那么i家也能偷int res1 = f[i - 1];int res2 = f[i - 2] + nums[i];// 偷较大值f[i] = Math.max(res1, res2);}// 最后的结果,在最后一家判断完之后,包含了所有的情况。// 所以答案在最后一个索引的位置。return f[n - 1];}
}

思路:

把f数组索引直接+2。上一篇代码的f[0]的值,放到这一题代码的f[2]的位置。

不管f[0]和f[1],直接不用他们。

class Solution {public int rob(int[] nums) {int n = nums.length;int[] f = new int[n + 2];for (int i = 0; i < n; i++) {f[i + 2] = Math.max(f[i + 1], f[i] + nums[i]);}return f[n + 1];}
}

思路:

空间优化的写法。

class Solution {public 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;}
}

这些都是写完之后的优化版本,如果第一次写,不要想复杂的,先按照自己内心第一思路,把题目写完先,再想优化。

213. 打家劫舍 II

方法:动态规划(递推)

思路:

  • 特判索引0位置,把剩余位置变为非循环数组进行操作。
  • 索引0位置只有两种选择,偷或者不偷。
    • 偷索引0,那么索引1和索引n-1不能偷。问题变为从索引2到n-2的非循环数组。
    • 不偷索引0。问题变为从索引1到n-1的非循环数组。
  • 取二者较大值
class Solution {public int rob(int[] nums) {int n = nums.length;// 偷索引0,那么索引1和索引n-1不能偷。问题变为从索引2到n-2的非循环数组。int res1 = nums[0] + rob1(nums, 2, n - 2);// 不偷索引0。问题变为从索引1到n-1的非循环数组。int res2 = rob1(nums, 1, n - 1);// 取二者较大值return Math.max(res1, res2);}// 上一题的空间优化版本private int rob1(int[] nums, int start, int end) {int f0 = 0;int f1 = 0;// [start,end] 左闭右闭for (int i = start; i <= end; i++) {int newF = Math.max(f1, f0 + nums[i]);f0 = f1;f1 = newF;}return f1;}
}

2320. 统计放置房子的方式数

方法:动态规划(递推)

思路:

  • 两边互相独立,求一边,最后f[n] * f[n]即可。
  • 初始化f[0]和f[1]。如果没有空地,只有不放这1种选择;如果只有一块空地,有放和不放2种选择。
  • 对于每个f[i]:
    • 情况一:如果不放f[i]的话,f[i-1]可放可不放
    • 情况二:如果放f[i]的话, f[i-2]可放可不放
    • 两种情况加起来,就是f[i]可放可不放
  • 最后返回结果时:
    • 注意,两个MOD相加,不会溢出int。但是两个MOD相乘,会溢出int
    • 所以要先转为long,相乘,取模,后再转为int
class Solution {public int countHousePlacements(int n) {// 两边互相独立,求一边,最后f[n] * f[n]即可final int MOD = 1000000007;int[] f = new int[n + 2];// 初始化。如果没有空地,只有不放这1种选择;如果只有一块空地,有放和不放2种选择。f[0] = 1;f[1] = 2;for (int i = 2; i <= n; i++) {// 情况一:如果不放f[i],f[i-1]可放可不放// 情况二:如果放f[i], f[i-2]可放可不放// 两种情况加起来,就是f[i]可放可不放f[i] = (f[i - 1] + f[i - 2]) % MOD;}// 注意,两个MOD相加,不会溢出int。但是两个MOD相乘,会溢出int// 所以要先转为long,相乘,取模,后再转为intreturn (int) ((long) f[n] * f[n] % MOD);}
}

总感觉这道题跟《打家劫舍》没什么关系,反而像是《爬楼梯》。每个位置都可爬可不爬,然后结果加起来。f[i] = (f[i - 1] + f[i - 2])。初步是这么想,多做题,回头再看看。

关于取模运算,可以看@灵艾山茶府的这篇文章:模运算的世界:当加减乘除遇上取模。

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

相关文章:

  • 机器人视觉检测
  • 151.翻转字符串里的单词(字符串算法)
  • 昇腾算力加持,深度思考模型Colossal-R1上线魔乐社区
  • 多智能体框架(下)
  • 嵌入式Linux驱动开发 - 蜂鸣器驱动
  • 【前端教程】JavaScript 数组对象遍历与数据展示实战
  • 微功耗遥测终端机在城市管网压力/流量监测中的应用
  • 打造企业内部的“技术桥梁”:超级用户机制如何助力制造企业高效运维
  • 【数据分享】省级人工智能发展水平综合指标体系(2011-2022)
  • 【LeetCode】动态规划——72.编辑距离、10.正则表达式匹配
  • ros2---位姿转换--eigen/tf2
  • 如何在mysql中执行创建数据库的脚本文件?
  • 企业级数据库管理实战(三):数据库性能监控与调优的实战方法
  • 学习笔记-Record类
  • 忆联参与制定消费级SSD团体标准正式出版! 以“高可靠”引领行业提质增效与用户体验升级
  • 联想打印机2268w安装
  • Ubuntu22.04系统安装Opencv,无法定位包libjasper-dev libdc1394-22-dev的解决办法
  • 微信小程序调用蓝牙打印机教程(TSPL命令)
  • 死锁检测 及其测试用例
  • 地铁隧道病害智能巡检系统——机器视觉技术的深度应用
  • Idea2025.2 MybatisX插件失效问题
  • vue3+wangEditor实现富文本编辑器
  • cursor的setting設置換行
  • 命令拓展(草稿)
  • Vue开发准备
  • Silvaco TCAD | Victory DoE的基本使用方法(三)
  • nacos单机部署并开启鉴权
  • 2025.8.29机械臂实战项目
  • Windows 下 MSYS2 + MinGW-w64 配置 Fyne GUI 编译环境全流程
  • Redis-分布式缓存