参考leetcode题解https://leetcode-cn.com/problems/house-robber-iii/solution/tong-yong-si-lu-tuan-mie-da-jia-jie-she-wen-ti-b-2/
LeetCode 「打家劫舍」系列问题(英文版叫 House Robber)是比较有代表性和技巧性的动态规划题目,今天就来聊聊这道题目。
打家劫舍系列总共有三道,难度设计非常合理,层层递进。第一道是比较标准的动态规划问题,而第二道融入了环形数组的条件,第三道更绝,把动态规划的自底向上和自顶向下解法和二叉树结合起来,我认为很有启发性。如果没做过的朋友,建议学习一下。
House Robber I
你是一个专业的盗贼,计划打劫某一条街一排的房屋。每个房屋都藏有一定的现金。但是如果两间相邻的房屋在同一晚被盗贼闯入的话,系统会自动警报。给定一个非负整数数组,计算在不触动警报装置的情况下,能够偷窃到的最高金额。例如:
输入:[1,2,3,1]
输出:4
解题思路
每到一个房间,有两个选择:1) 抢+继续抢下下个房间;2) 从下一个房间抢
两个选择中,每次都选更大的结果,最后得到的就是最多能抢到的。
// 递归思路
public int rob(int[] nums){
return db(nums, 0);
}
int dp(int[] nums, int start){
if(start>=nums.lengh)
return 0;
int res = max(dp(nums, start+1), nums[start]+dp(nums, start+2));
return res;
}
// 使用备忘录,重复子问题不再计算
int[] memo;
int rob(int[] nums){
memo = new int[nums.length];
Arrays.fill(memo, -1);
return dp(nums, 0);
}
int dp(int[] nums, int start){
if(start>=nums.length)
return 0;
// 优先查找备忘录
if(memo[start] != -1)
return memo[start];
int ret = max(dp(nums, start+1), nums[start]+dp(nums, start+2));
// 记入备忘录
memo[start] = ret;
return ret;
}
// dp自底向上
int rob(int[] nums){
int dp_i1 = 0, dp_i2 = 0, dp_i = 0;
for(int i=nums.length-1; i>=0; i--){
dp_i = max(nums[i]+dp_i2, dp_i1);
dp_i1 = dp_i;
dp_i2 = dp_i1;
}
return dp_i;
}
Hourse Robber II
题目与第一道描述基本一样,输入仍为一个数组,但房子是围成了一个圈。也就是说第一间房子和最后一间房子围成了一个圈。
解题思路
这里首尾的房子不能被同时抢,所以就有三种情况:都不被抢;最后一间房子不能抢;第一间房子不能抢。第一种情况被后面两种包含了,所以该题可拆成两个子问题,每个子问题都可由题1解决。
int rob(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];
return Math.max(robRange(nums, 0, n - 2),
robRange(nums, 1, n - 1));
}
// 仅计算闭区间 [start,end] 的最优结果
int robRange(int[] nums, int start, int end) {
int n = nums.length;
int dp_i_1 = 0, dp_i_2 = 0;
int dp_i = 0;
for (int i = end; i >= start; i--) {
dp_i = Math.max(dp_i_1, nums[i] + dp_i_2);
dp_i_2 = dp_i_1;
dp_i_1 = dp_i;
}
return dp_i;
}
House Robber III
这里强盗面对的房子不是一排,不是一圈,而是一棵二叉树,每个节点都是一个房子,相连的房子不能同时被抢劫。
int rob(TreeNode root){
int[] res = dp(root);
return max(res[0], res[1]);
}
// 返回一个大小为2的数组arr
// arr[0]表示不抢root的话,得到的最大收益
// arr[1]表示抢root的话,得到的最大收益
int [] dp(TreeNode root){
if(root==null)
return new int[]{0, 0};
int [] left = dp(root.left);
int right = dp(root.right);
// 抢root
int rob = root.val + left[0] + right[0];
// 不抢root
int not_rob = max(left[0], left[1]) + max(right[0], right[1]);
return new int[]{not_rob, rob};
}