打家劫舍系列问题
打家劫舍一
思路
显然,对于每家小偷都有打或不打两种情况,用dp[i][j]表示前i家小偷打劫的最大收益,并用j标记是否打劫第i家,于是很容易想到如下递推式:
dp[i][0] = max(dp[i-1][0], dp[i-1][1]);
dp[i][1] = max(dp[i-1][0], dp[i-2][1]) + nums[i]
该式子又可以简化一下:
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
class Solution {
public int rob(int[] nums) {
int N = nums.length;
if(N == 0)
return 0;
int dp[][] = new int[N][2];
for(int i = 0; i < N; i++){
if(i == 0){
dp[i][0] = 0;
dp[i][1] = nums[i];
continue;
}
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]);
if(i == 1){
dp[i][1] = nums[i];
}
else
dp[i][1] = Math.max(dp[i-1][0], dp[i-2][1]) + nums[i];
}
return Math.max(dp[N-1][0], dp[N-1][1]);
}
}
滚动数组
/*
著名打家劫舍问题
dp[i][0] = max(dp[i-1][0], dp[i-1][1])
dp[i][1] = max(dp[i-1][0], dp[i-2][1]) + nums[i]
dp[i] = max(dp[i-1], dp[i-2];)
=>
dp[i] = max(dp[i-1], dp[i-2] + nums[i])
*/
class Solution {
public int rob(int[] nums) {
int N = nums.length;
if(N == 0)
return 0;
int dp = nums[0];
int first = dp;
int second = 0;
for(int i = 1; i < N; i++){
dp = Math.max(first, second + nums[i]);
int tmp = first;
first = dp;
second = tmp;
}
return dp;
}
}
打家劫舍二
问题2变成了房子围成了一圈,这种情况下,打劫第一家就不能打劫最后一家,打劫最后一家就不能打劫第一家,于是又转换求两个问题1的最大值
class Solution {
public int rob(int[] nums) {
int N = nums.length;
if(N == 0)
return 0;
if(N == 1)
return nums[0];
int dp[][] = new int[N][2];
//环形可分成两种情况:打劫第一家最后一家不能打劫 和 不打劫第一家 最后一家可以打劫
//第一家打劫
int ans = Integer.MIN_VALUE;
dp[0][0] = Integer.MIN_VALUE;
dp[0][1] = nums[0];
//打劫第一家 最后一家不能打劫
for(int i = 1; i < N-1; i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]);
//打劫第一家就不能打劫第二家了
if(i == 1)
dp[i][1] = Integer.MIN_VALUE;
else
dp[i][1] = Math.max(dp[i-1][0], dp[i-2][1]) + nums[i];
}
if(N == 2){
ans = dp[0][1];
}
else{
ans = Math.max(dp[N-2][0], dp[N-2][1]);
}
//第一家不能打劫
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
for(int i = 1; i < N; i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]);
if(i == 1)
dp[i][1] = nums[i];
else dp[i][1] = Math.max(dp[i-1][0], dp[i-2][1]) + nums[i];
}
ans = Math.max(ans, dp[N-1][0]);
ans = Math.max(ans, dp[N-1][1]);
return ans;
}
}
滚动数组
class Solution {
public int rob(int[] nums) {
int N = nums.length;
//打劫第一家
int dp = nums[0];
int first = dp;
int second = 0;
int ans = 0;
for(int i = 1; i < N-1; i++){
dp = Math.max(first, second + nums[i]);
int tmp = first;
first = dp;
second = tmp;
}
ans = Math.max(ans, dp);
dp = 0;
first = 0;
second = 0;
for(int i = 1; i < N; i++){
dp = Math.max(first, second + nums[i]);
int tmp = first;
first = dp;
second = tmp;
}
return Math.max(ans, dp);
}
}
打家劫舍三
树形dp问题
明日再更奥
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
Map<TreeNode, Integer> memo = new HashMap<>();
public int rob(TreeNode root) {
if(root == null)
return 0;
//利用备忘录消除重叠子问题
if(memo.containsKey(root)) return memo.get(root);
//抢它! 那么下次就得从隔壁的隔壁的节点开始抢
int do_it = root.val + (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right))
+ (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right));
//不抢 那么就可以从隔壁开始抢
int not_do = rob(root.left) + rob(root.right);
int res = Math.max(do_it, not_do);
memo.put(root, res);
return res;
}
}