House Robber
典型的动规问题,一开始设置了数组dp[len],后来可以发现只跟2个数有关,可以用%来将空间复杂度降低到O(1) 根据答案进一步可以发现,其实当前值也无关,看第二个解法中,可以把curr理解成前一个值,而prev就是前前一个
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[2];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i % 2] = Math.max(dp[(i-1) % 1], dp[(i-2) % 2] + nums[i]);
}
return dp[(nums.length - 1) % 2];
}
}
public int rob(int[] num) {
int prevMax = 0;
int currMax = 0;
for (int x : num) {
int temp = currMax;
currMax = Math.max(prevMax + x, currMax);
prevMax = temp;
}
return currMax;
}
House Robber II
第一题的follow up,如果数组是一个环,头尾相接怎么办? 遇到数组为环的时候,可以用拆环的办法 (另一种办法是double 利用已经做好的第一个题的办法,只要考虑边界情况
- 选了第一个,不选最后一个
- 选最后一个,不选第一个
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
return Math.max(rob(nums, 0, nums.length - 2), rob(nums, 1, nums.length - 1));
}
public int rob(int[] nums, int start, int end) {
int prevMax = 0;
int currMax = 0;
for (int i = start; i <= end; i++) {
int temp = currMax;
currMax = Math.max(prevMax + nums[i], currMax);
prevMax = temp;
}
return currMax;
}
}
House Robber III
对于在树上,不能有相邻的节点
- 一个错误的理解是只要考虑隔层的情况,忽略了可能存在取第一层,第四层这样的。所以不能用bfs
最简单直接的想法是利用递归,rob(root)是取决于
- rob(root.left), rob(root.right)
- root + rob(root.left.left) .... + rob(root.right.right) (a.k.a root's grandchildren)
然而这个解法有了很多的重复计算,复杂度为O(n+n的左子树总数+n的右子树总数+
The time complexity for the naive recursion in step I is indeed exponential. For each tree node tn at depth d, let T(d) be the number of times the rob function will be called on it. Then we have T(d) = T(d - 1) + T(d - 2). This is because rob will be called on tn either from its parent (at depth d - 1) or its grandparent (at depth d - 2), according to its definition. Note T(0) = T(1) = 1, i.e., rob will be called only once for the tree root and its two child nodes. Therefore T(d) will essentially be the (d+1)-th Fibonacci number (starting from 1), which grows exponentially (more info can be found here).
进而考虑到利用HashMap解决重复计算的问题,保存每个点已经计算过的值,确保每个点只用算一次O(n)
再进一步分析,发现主要需要考虑的是两种情况,root是否被rob,只需要保持这两个信息的可能传递下去。所以考虑建立一个返回值为int[]的helper,其中
- int[0]为root没有被rob的情况,这只和root.left和root.right的int[]有关
- int[1]为root被rob的情况,那么这就只和left[0]和right[0]有关
class Solution {
public int rob(TreeNode root) {
if (root == null) return 0;
int val = 0;
if (root.left != null) {
val += rob(root.left.left) + rob(root.left.right);
}
if (root.right != null) {
val += rob(root.right.left) + rob(root.right.right);
}
return Math.max(root.val + val, rob(root.left) + rob(root.right));
}
}
//HashMap, a naive DP solution
public int rob(TreeNode root) {
return robSub(root, new HashMap<>());
}
private int robSub(TreeNode root, Map<TreeNode, Integer> map) {
if (root == null) return 0;
if (map.containsKey(root)) return map.get(root);
int val = 0;
if (root.left != null) {
val += robSub(root.left.left, map) + robSub(root.left.right, map);
}
if (root.right != null) {
val += robSub(root.right.left, map) + robSub(root.right.right, map);
}
val = Math.max(val + root.val, robSub(root.left, map) + robSub(root.right, map));
map.put(root, val);
return val;
}
//
public int rob(TreeNode root) {
int[] res = robSub(root);
return Math.max(res[0], res[1]);
}
private int[] robSub(TreeNode root) {
if (root == null) return new int[2];
int[] left = robSub(root.left);
int[] right = robSub(root.right);
int[] res = new int[2];
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = root.val + left[0] + right[0];
return res;
}