间隔遍历(打家劫舍III)
3
/ \
2 3
\ \
3 1
Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
暴力递归
思路:
-
对于一个以 node 为根节点的二叉树而言,如果尝试偷取 node 节点,那么势必不能偷取其左右子节点,然后继续尝试偷取其左右子节点的左右子节点。
-
如果不偷取该节点,那么只能尝试偷取其左右子节点。
-
比较两种方式的结果,谁大取谁。
class Solution {
public int rob(TreeNode root) {
if (root == null) return 0;
int money = root.val;
if (root.left != null){
money += rob(root.left.left) + rob(root.left.right);
}
if (root.right != null){
money += rob(root.right.left) + rob(root.right.right);
}
return Math.max(money, rob(root.left) + rob(root.right));
}
}
解决重复子问题,记忆优化
class Solution {
public int rob(TreeNode root) {
HashMap<TreeNode, Integer> memo = new HashMap<>();
return robInternal(root, memo);
}
public int robInternal(TreeNode root, HashMap<TreeNode, Integer> memo) {
if (root == null) return 0;
if (memo.containsKey(root)) return memo.get(root);
int money = root.val;
if (root.left != null) {
money += (robInternal(root.left.left, memo) + robInternal(root.left.right, memo));
}
if (root.right != null) {
money += (robInternal(root.right.left, memo) + robInternal(root.right.right, memo));
}
int result = Math.max(money, robInternal(root.left, memo) + robInternal(root.right, memo));
memo.put(root, result);
return result;
}
}
动态规划
每个节点可选择偷或者不偷两种状态,根据题目意思,相连节点不能一起偷
- 当前节点选择偷时,那么两个孩子节点就不能选择偷了
- 当前节点选择不偷时,两个孩子节点只需要拿最多的钱出来就行(两个孩子节点偷不偷没关系)
我们使用一个大小为 2 的数组来表示 int[] result = new int[2]
;
0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为
- 当前节点选择不偷:当前节点能偷到的最大钱数 = 左右孩子偷或不偷得到的钱=
Math.max{left[0],left[1]} + Math.max{right[0],right[1]}
- 当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数=
left[0] + right[0]
class Solution {
public int rob(TreeNode root) {
int[] result = dfs(root);
return Math.max(result[0],result[1]);
}
private int[] dfs(TreeNode root){
if (root == null) {
return new int[2];
}
int[] result = new int[2];
int[] left = dfs(root.left);
int[] right = dfs(root.right);
result[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);
result[1] = root.val + left[0] + right[0];
return result;
}
}
打家劫舍
Example 1:
Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 2:
Input: nums = [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.
dp[i]
:从 i 个房子偷到的最大金额
状态转移方程:dp[i] = max{dp[i-2] + nums[i],dp[i]}
class Solution {
public int rob(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int[] dp = new int[nums.length];
if(nums.length == 1) return nums[0];
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];
}
}
空间优化
对于小偷问题,我们发现,最后一步计算 f(n) 的时候,实际上只用到了 f(n-1) 和 f(n-2) 的结果。n-3 之前的子问题,实际上早就已经用不到了。那么,我们可以只用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题。下面的动图比较了空间优化前和优化后的对比关系:
class Solution {
public int rob(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int pre = 0,cur = 0;
// 循环开始时,cur 表示 dp[k-1],pre 表示 dp[k-2]
// dp[k] = max{ dp[k-1], dp[k-2] + nums[i]}
for (int i : nums
) {
int temp = Math.max(pre + i, cur);
pre = cur;
cur = temp;
}
// 循环结束时,curr 表示 dp[k],prev 表示 dp[k-1]
return cur;
}
}
时间复杂度O(n),空间复杂度O(1)
或者
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
}
打家劫舍II(首尾相邻,不能同时偷)
class Solution {
public int rob(int[] nums) {
if (nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
//数组复制,下标左闭右开
return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)),
myRob(Arrays.copyOfRange(nums, 1, nums.length)) );
}
private int myRob(int[] nums){
int pre = 0,cur = 0;
for (int i : nums){
int temp = Math.max(pre + i, cur);
pre = cur;
cur = temp;
}
return cur;
}
}