数组复制可以用Arrays.copyOfRange(arr,start,end), 指定数组区间
此外,这个问题是将环状问题约化为两个单排列问题。
-
状态定义:
设动态规划列表 dp ,dp[i] 代表前 i 个房子在满足条件下的能偷窃到的最高金额。 -
转移方程:
dp[n+1] = max(dp[n],dp[n-1]+num)
此处既然dp[n+1]只和前两个状态dp[n-1]和dp[n]有关,所以可以用常数级变量优化空间复杂度
即:
今天偷钱后最大值 = 昨天没偷钱最大值+今天钱数
今天没偷钱最大值 = Math.max(昨天偷钱后最大值,昨天没偷钱后最大值)
最后取两者较大值返回Math.max(今天偷钱后最大值,今天没偷钱最大值)
复杂度分析:
时间复杂度 O(N) : 两次遍历 nums 需要线性时间;
空间复杂度 O(1) : curRob和 curNoRob 使用常数大小的额外空间。
class Solution {
public int rob(int[] nums) {
if(nums.length==1) return nums[0];
return Math.max(robHouse(Arrays.copyOfRange(nums,0,nums.length-1)),
robHouse(Arrays.copyOfRange(nums,1,nums.length)));
}
public int robHouse(int[] nums){
int curRob=0,curNoRob=0,preRob;
for(int num:nums){
preRob = curRob;
curRob = curNoRob+num;
curNoRob = Math.max(preRob,curNoRob);
}
return Math.max(curRob,curNoRob);
}
}
下面这个问题,是对上一个问题的升级
方法一是在递归的基础上,增加了记忆集来提升查询速度,没想到竟然超时了。。。
方法二是直接动态规划:
定义数组 int[] res = new int[2] ,其中0 代表不偷,1 代表偷。任何一个节点能偷到的最大钱的状态可以定义为:
- 当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
- 当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
class Solution {
//方案一:递归+记忆集
public int rob(TreeNode root) {
if(root==null) return 0;
return recurRob(root,new HashMap<TreeNode,Integer>());
}
public int recurRob(TreeNode root,HashMap<TreeNode,Integer> maxMoney){
if(root==null) return 0;
if(maxMoney.containsKey(root)) return maxMoney.get(root);
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);
}
int max = Math.max(money,rob(root.left)+rob(root.right));
maxMoney.put(root,max);
return max;
}
//方案二:
public int rob(TreeNode root){
int[] res = recurRob(root);
return Math.max(res[0],res[1]);
}
public int[] recurRob(TreeNode root){
if(root==null) return new int[2];
int[] res = new int[2];
int[] left = recurRob(root.left);
int[] right = recurRob(root.right);
res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
res[1] = left[0]+right[0]+root.val;
return res;
}
}