这个系列有好几道,一个一个来!
难度:简单
题目描述
解题思路
这是这个系列里最简单的一道啦,状态转移方程很明显。对于第i个房子,要么偷要么不偷,如果偷那金额就等于前两天的金额+当前房子的金额;如果不偷那就等于前一天的金额。每天从这两种选择中选出金额最大的,写成状态转移方程就是:
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
代码也挺简短的,注意边界条件和数组下标,时间和空间复杂度都是O(N)
public int rob(int[] nums) {
int n = nums.length;
if(n == 0)
return 0;
int[] dp = new int[n+1];
dp[1] = nums[0]; //初始化第一天
//今天偷 dp[i-2]+nums[i] 不偷 dp[i-1]
for(int i = 2;i < n + 1;i++){
dp[i] = Math.max(dp[i-2] + nums[i-1],dp[i-1]);
}
return dp[n];
}
状态压缩 因为当前状态只和前一天和前两天的金额有关,所以可以只用两个变量来分别存储,把空间复杂度从O(N)压缩成O(1)
public int rob(int[] nums) {
int n = nums.length;
if(n == 0)
return 0;
int pre1 = 0,pre2 = 0,max = 0; //前一天,前两天
pre1 = nums[0]; //初始化第一天
//今天偷 pre2+nums[i] 不偷 pre1
for(int i = 1;i < n;i++){
max = Math.max(pre2 + nums[i],pre1);
pre2 = pre1;
pre1 = max;
}
return pre1;
}
难度:中等
题目描述
解题思路
这道题和第一道的区别在于房屋围成一圈,最后一个房屋和第一个是紧挨着对,所以如果偷了第一间屋子,就不能偷最后一间。
所以其实就是把数组分成两个,一个0 ~ n-1,一个1 ~ n,在这两个范围内分别用动态规划求值,然后取两者最大值,可以直接用上一道题的代码
public int rob(int[] nums) {
int n = nums.length;
if(0 == n)
return 0;
if(1 == n)
return nums[0];
return Math.max(massage(Arrays.copyOfRange(nums, 0, n - 1)), massage(Arrays.copyOfRange(nums, 1, n)));
}
public int massage(int[] nums) {
int n = nums.length;
if(n == 0)
return 0;
int pre1 = 0,pre2 = 0,max = 0; //前一天,前两天
pre1 = nums[0]; //初始化第一天
//今天偷 pre2+nums[i] 不偷 pre1
for(int i = 1;i < n;i++){
max = Math.max(pre2 + nums[i],pre1);
pre2 = pre1;
pre1 = max;
}
return pre1;
}
难度:中等
题目描述
解题思路
这年头当小偷真不容易.
写了前面两道题,再看到这个,心想这不是小意思嘛,先层序遍历得到每一层的和,然后再用上面的动态规划求最大值,信心满满一提交,结果这个用例让我傻眼了。一层不一定要一次性全抢完
然后还是看了评论,应该用树形dp来做
参考题解:三种方法解决树形动态规划问题-从入门级代码到高效树形动态规划代码实现
这篇写的很清楚啦~先用普通递归,然后记忆化递归,然后消除后效性
public int rob(TreeNode root) {
if(root == null)
return 0;
int cur = root.val;
int left = rob(root.left);
int right = rob(root.right);
if(root.left != null) { //偷左子树的两个孩子
cur += rob(root.left.left) + rob(root.left.right);
}
if(root.right != null) { //偷右子树的两个孩子
cur += rob(root.right.left) + rob(root.right.right);
}
return Math.max(left+right, cur);
}
思路也很清楚啦,要么偷当前根节点和四个孙子,要么偷根节点的左右孩子,取最大值。这样有很多重复的计算,可以用记忆化递归进行优化
用一个哈希表来存储每个节点已经计算出来的值,如果有记录就直接取值
public int rob(TreeNode root) {
if(root == null)
return 0;
HashMap<TreeNode, Integer> memo = new HashMap<>();
return robHelper(root, memo);
}
public int robHelper(TreeNode root,HashMap<TreeNode, Integer> memo) {
if(root == null)
return 0;
if(memo.containsKey(root))
return memo.get(root);
int cur = root.val;
int left = robHelper(root.left,memo);
int right = robHelper(root.right,memo);
if(root.left != null) { //偷左子树的两个孩子
cur += robHelper(root.left.left,memo) + robHelper(root.left.right,memo);
}
if(root.right != null) { //偷右子树的两个孩子
cur += robHelper(root.right.left,memo) + robHelper(root.right.right,memo);
}
int re = Math.max(left+right, cur);
memo.put(root, re);
return re;
}
public int rob(TreeNode root) {
int [] re = robHelper(root);
return Math.max(re[0], re[1]);
}
public int[] robHelper(TreeNode root) {
if(root == null)
return new int[] {0,0};
int[] re = new int[2];
int rob = 0,norob = 0;
//抢了这个节点,就不能抢他的孩子;
int[] left = robHelper(root.left);
int[] right = robHelper(root.right);
rob = root.val + left[1] + right[1];
//关键在于如果不抢这个节点,对左右两个节点可以选择抢一个或者两个都抢,取可能情况的最大值
norob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
re[0] = rob;
re[1] = norob;
return re;
}