目录
198. 打家劫舍
解法一:一维动态规划
解法二:二维动态规划
213. 打家劫舍 II
思路分析
代码实现
337. 打家劫舍 III
思路分析
代码实现
2560. 打家劫舍 IV
思路分析
参考博客
198. 打家劫舍
如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 ,求今晚能够偷窃到的最高金额。
解法一:一维动态规划
class Solution {public int rob(int[] nums) {if (nums == null || nums.length == 0) {return 0;}int n = nums.length;int dp[] = new int[n]; if (n == 1) {return nums[0];}dp[0] = nums[0]; dp[1] = Math.max(nums[0],nums[1]); for(int i=2;i<n;i++){// 不偷这个房子,那么总金额就是偷到前一个房子为止的最大金额,即 dp[i-1]。// 偷这个房子,那么总金额就是这个房子的金额加上偷到前前一个房子为止的最大金额,即 nums[i] + dp[i-2]。dp[i] = Math.max(dp[i-1], nums[i] + dp[i-2]);}return dp[n-1];}
}
解法二:二维动态规划
参考这篇博客的方法三LeetCode122之股票买卖的最好时机(相关话题:动态规划,记忆搜索,状态机,贪心算法)_最适合买入股票算法题-CSDN博客
class Solution {public int rob(int[] nums) {int n = nums.length;//dp[i][0]表示不偷第i个房间,dp[i][1]表示偷第i个房间int dp[][] = new int[n][2]; dp[0][0] = 0; dp[0][1] = nums[0]; for(int i=1;i<n;i++){dp[i][0] = Math.max(dp[i-1][0] , dp[i-1][1]);dp[i][1] = dp[i-1][0] + nums[i];}return Math.max(dp[n-1][0],dp[n-1][1]);}
}
213. 打家劫舍 II
这个地方所有的房屋都 围成一圈 ,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 , 求今晚能够偷窃到的最高金额。
思路分析
- 第一次考虑时,我们假设偷了第一间房屋,因此不能偷最后一间房屋。这样,我们就只考虑从第一间房屋到倒数第二间房屋的数组范围,即 nums[0] 到 nums[n-2]。
- 第二次考虑时,我们假设不偷第一间房屋,这样就可以偷最后一间房屋。这时,我们考虑的数组范围是从第二间房屋到最后一间房屋,即 nums[1] 到 nums[n-1]。
代码实现
基于(Python)
class Solution:def rob(self, nums: List[int]) -> int:# 辅助函数,用于处理不成环的情况def rob_linear(houses):n = len(houses)if n == 1:return houses[0]if n == 2:return max(houses[0], houses[1])dp = [0] * ndp[0] = houses[0]dp[1] = max(houses[0], houses[1])for i in range(2, n):dp[i] = max(dp[i-1], dp[i-2] + houses[i])return dp[-1]# 如果房屋数量小于3,直接返回最大金额的房子n = len(nums)if n == 1:return nums[0]if n == 2:return max(nums[0], nums[1])# 第一次考虑时,我们假设偷了第一间房屋,因此不能偷最后一间房屋。这样,我们就只考虑从第一间房屋到倒数第二间房屋的数组范围,即 nums[0] 到 nums[n-2]。#第二次考虑时,我们假设不偷第一间房屋,这样就可以偷最后一间房屋。这时,我们考虑的数组范围是从第二间房屋到最后一间房屋,即 nums[1] 到 nums[n-1]。return max(rob_linear(nums[:-1]), rob_linear(nums[1:]))
337. 打家劫舍 III
root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
思路分析
- 如果偷当前节点,那么子节点就不能偷
- 如果不偷当前节点,那么子节点可以偷也可以不偷,取决于哪种选择更优
- 如果函数只返回一个值可能会超时,需要引入缓存,可以考虑函数分别返回访问当前节点的最大收益,和不访问当前节点最大收益数组
代码实现
只有一个返回值加缓存的实现
class Solution {public int rob(TreeNode root) {// 使用HashMap来存储节点在被访问和未被访问时的最大值Map<TreeNode, Integer>[] memo = new Map[2];memo[0] = new HashMap<>(); // 不偷当前节点的情况memo[1] = new HashMap<>(); // 偷当前节点的情况int maxProfit = Math.max(helpRob(root,1,memo), helpRob(root,0,memo));return maxProfit;}private int helpRob(TreeNode root, int canRob, Map<TreeNode, Integer>[] memo) {if(root==null){return 0;}// 检查缓存是否已有结果if (memo[canRob].containsKey(root)) {return memo[canRob].get(root);}int maxProfit = 0;if(canRob==1){// 如果偷当前节点,那么子节点就不能偷maxProfit = Math.max(maxProfit ,root.val + helpRob(root.left,0,memo) + helpRob(root.right,0,memo));}else{int temp1 = helpRob(root.left,0,memo);int temp2 = helpRob(root.left,1,memo);int temp3 = helpRob(root.right,0,memo);int temp4 = helpRob(root.right,1,memo);// 如果不偷当前节点,那么子节点可以偷也可以不偷,取决于哪种选择更优maxProfit = Math.max(temp1+temp3,temp1+temp4);maxProfit = Math.max(maxProfit,temp2+temp3);maxProfit = Math.max(maxProfit,temp2+temp4);}// 将结果存入缓存memo[canRob].put(root, maxProfit);return maxProfit;}}
返回值是数组的实现
class Solution {public int rob(TreeNode root) {int[] result = robSub(root);return Math.max(result[0], result[1]);}//res[0] 表示不偷当前节点时的最大值。//res[1] 表示偷当前节点时的最大值。private int[] robSub(TreeNode node) {if (node == null) {return new int[2];}int[] left = robSub(node.left);int[] right = robSub(node.right);int[] res = new int[2];// 如果不偷当前节点,那么子节点可以偷也可以不偷,取决于哪种选择更优res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);// 如果偷当前节点,那么子节点就不能偷res[1] = node.val + left[0] + right[0];return res;}
}
2560. 打家劫舍 IV
沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。给你一个整数数组 nums 表示每间房屋存放的现金金额。形式上,从左起第 i 间房屋中放有 nums[i] 美元。另给你一个整数 k ,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k 间房屋。返回小偷的 最小 窃取能力。
思路分析
单调性如果小偷可以在某个窃取能力x
下窃取至少k
间房屋,那么他也能在任何大于x
的能力下做到同样的事情。这是因为增加窃取能力意味着更多房屋的金额成为可选项。这种单调递增关系是二分查找可行性的关键。
参考博客
LeetCode887之鸡蛋掉落(相关话题:动态规划,二分法)_鸡蛋掉落二分法是否有效率提高的空间?包括空间效率和时间效率。-CSDN博客
class Solution {public int minCapability(int[] nums, int k) {// 初始化二分搜索的边界int lower = Arrays.stream(nums).min().getAsInt(); // 找到数组中的最小值,作为最低可能的窃取能力int upper = Arrays.stream(nums).max().getAsInt(); // 找到数组中的最大值,作为最高可能的窃取能力// 执行二分搜索while (lower <= upper) {int middle = (lower + upper) / 2; // 计算中间值作为当前的窃取能力int count = 0; // 用来计数在当前窃取能力下可以窃取的不相邻房屋数boolean visited = false; // 用于标记上一间房屋是否已被窃取// 遍历房屋数组,判断在当前窃取能力下的窃取情况for (int x : nums) {if (x <= middle && !visited) {// 如果当前房屋的金额不超过middle,并且上一间房屋没有被窃取count++; // 增加计数visited = true; // 标记当前房屋已被窃取} else {// 如果当前房屋的金额超过middle或上一间房屋已被窃取visited = false; // 重置visited,表示当前房屋没有被窃取}}// 判断是否能满足至少窃取k间房屋的条件if (count >= k) {// 如果可以窃取的房屋数量大于等于kupper = middle - 1; // 降低窃取能力的上界,尝试找到更小的窃取能力} else {// 如果不能满足窃取至少k间房屋lower = middle + 1; // 增加窃取能力的下界,尝试更高的窃取能力}}// 当lower > upper时,lower即为所求的最小窃取能力return lower;}
}