198.打家劫舍
题目链接:https://leetcode.cn/problems/house-robber/
文档讲解:https://www.programmercarl.com/0198.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8D.html
视频讲解:https://www.bilibili.com/video/BV1Te411N7SX
思路
- 确定dp数组以及下标的含义:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为
dp[i]
。 - 确定递推公式:如果偷下标为i的房间,那么就和下标为i-1的房间无关,为
dp[i - 2] + nums[i]
;如果不偷下标为i的房间,说明偷的是下标i - 1的房间,为dp[i - 1]
。 - dp数组如何初始化:
dp[0] = nums[0];
,dp[1] = Math.max(nums[0], nums[1]);
- 确定遍历顺序:从下标为2的房间正序遍历。
- 打印dp数组,用于debug
代码
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int[] dp = new int[nums.length];
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 - 2] + nums[i], dp[i - 1]);
}
return dp[nums.length - 1];
}
}
分析:时间复杂度:O(n),空间复杂度:O(n)。
213.打家劫舍II
题目链接:https://leetcode.cn/problems/house-robber-ii/
文档讲解:https://www.programmercarl.com/0213.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DII.html
视频讲解:https://www.bilibili.com/video/BV1oM411B7xq
思路
本题跟198的区别在于数组成环了,那么主要有如下三种情况:
- 情况一:考虑不包含首尾元素
- 情况二:考虑包含首元素,不包含尾元素
- 情况三:考虑包含尾元素,不包含首元素
而情况二 和 情况三 都包含了情况一了,所以只考虑情况二和情况三就可以了。
所以本题将198中的动态规划思路打包成一个函数,通过考虑不同的情况,向函数中传入不同的子数组就可以。
代码
class Solution {
public int rob(int[] nums) {
if (nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int num1 = robRange(nums, 0, nums.length - 2); // 考虑头
int num2 = robRange(nums, 1, nums.length - 1); // 考虑尾
return Math.max(num1, num2);
}
public int robRange(int[] nums, int start, int end) { // 左闭右闭
if (start == end) return nums[start];
int[] dp = new int[end - start + 1];
dp[0] = nums[start];
dp[1] = Math.max(nums[start], nums[start + 1]);
for (int i = 2; i < dp.length; i++) {
dp[i] = Math.max(dp[i - 2] + nums[start + i], dp[i - 1]);
}
return dp[dp.length - 1];
}
}
分析:时间复杂度:O(n),空间复杂度:O(n)。
337.打家劫舍 III
题目链接:https://leetcode.cn/problems/house-robber-iii/
文档讲解:https://www.programmercarl.com/0337.%E6%89%93%E5%AE%B6%E5%8A%AB%E8%88%8DIII.html
视频讲解:https://www.bilibili.com/video/BV1H24y1Q7sY
思路
使用后序遍历,因为需要返回左右孩子的结果。
其中用dp[0]
表示不偷当前节点,用dp[1]
表示偷当前节点。
res[1] = root.val + left[0] + right[0]; // 偷当前节点,则不偷左右孩子
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); // 不偷当前节点,对于左右孩子选偷或不偷中钱多的
代码
class Solution {
public int rob(TreeNode root) {
int[] res = traversal(root); // 定义数组中res[0]为不偷当前节点,res[1]为偷当前节点
return Math.max(res[0], res[1]);
}
public int[] traversal(TreeNode root) {
int[] res = new int[2];
if (root == null) return res;
int[] left = traversal(root.left); // 左
int[] right = traversal(root.right); // 右
// 中
res[1] = root.val + left[0] + right[0]; // 偷当前节点
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); // 不偷当前节点,对于左右孩子选偷或不偷中钱多的
return res;
}
}
分析:
时间复杂度:O(n),每个节点只遍历了一次。
空间复杂度:O(log n),算上递推系统栈的空间。