【动态规划】打劫家舍系列

第一题比较简单就不放了

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。

 

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。


示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。


示例 3:

输入:nums = [0]
输出:0
 

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 1000

来源:力扣(LeetCode)


 

开始感觉是不是用循环队列做,仔细想想不行,因为必须要全局最优。第一个和最后一个房间只能二选一或者全不选,一次遍历无法确定第一个和最后一个哪个更好。因此要遍历两次,分别保留第一个和最后一个房间,取更大的那个作为结果。

 1 class Solution {
 2     public int rob(int[] nums) {
 3         int len=nums.length;
 4         int yes=nums[0];//今天偷了
 5         int no=0;//今天没偷
 6         int preYes=yes;
 7         int preNo=no;
 8         for(int i=1;i<len-1;i++){//忽略最后
 9             
10             yes=preNo+nums[i];//今天要偷
11             no=Math.max(preYes,preNo);//今天不偷
12             preNo=no;
13             preYes=yes;
14         }
15         int res1=Math.max(yes,no);
16 
17         yes=nums[len-1];//今天偷了
18         no=0;//今天没偷
19         preYes=yes;
20         preNo=no;
21 
22         for(int i=len-2;i>0;i--){
23             
24             yes=preNo+nums[i];//今天要偷
25             no=Math.max(preYes,preNo);//今天不偷
26             preNo=no;
27             preYes=yes;
28         }
29 
30         int res2=Math.max(yes,no);
31         return Math.max(res1,res2);
32     }
33 }

 


337. 打家劫舍 III

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例 1:

输入: [3,2,3,null,3,null,1]

3
/ \
2 3
\ \
3 1

输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:

输入: [3,4,5,1,3,null,1]

  3
/ \
4 5
/ \ \
1 3 1

输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.

来源:力扣(LeetCode)


只想到了最暴力地递归方法,693ms就离谱,只超过了25%的提交

 1 /**
 2  * Definition for a binary tree node.
 3  * public class TreeNode {
 4  *     int val;
 5  *     TreeNode left;
 6  *     TreeNode right;
 7  *     TreeNode(int x) { val = x; }
 8  * }
 9  */
10 class Solution {
11     
12     public int rob(TreeNode root) {
13         
14         return dfs(root);
15         
16 
17 
18     }
19 
20     public int dfs(TreeNode root){//如果偷当前节点,只能选择偷左右子树的子树了;如果不偷,那么就偷左右子树。比较这两者大小。
21         if(root==null)return 0;
22         if(root.left==null&&root.right==null)return root.val;
23 
24         int ll=0;
25         int lr=0;
26         int rl=0;
27         int rr=0;
28         //四个子树的子树
29 
30         if(root.left!=null){
31             ll=dfs(root.left.left);
32             lr=dfs(root.left.right);
33         }
34         if(root.right!=null){
35             rl=dfs(root.right.left);
36             rr=dfs(root.right.right);
37         }
38 
39         return Math.max(ll+lr+rl+rr+root.val,dfs(root.left)+dfs(root.right));
40         
41     }
42 }

 


在评论区看到的一个大佬的思路,感觉看起来似乎和递归差不多,但是只用了1ms。两个代码的区别在于递归的分支数量。下面的只有两个分支,而上面的有六个分支,仔细体会。同时,使用数组定义状态,比用哈希表做缓存更加节省空间和时间。

 1 class Solution {
 2     
 3     public int rob(TreeNode root) {
 4         
 5         //动态规划
 6         //思路:
 7         //定义一个数组res,长度为2,res[0]表示不抢该节点可获得最大值,res[1]表示抢劫该节点可获得最大值
 8         //方法helper(r)意为:在以r为根节点的树中,返回抢劫根节点与不抢劫根节点可获得的最大值
 9         //执行用时 2ms
10         int[] res = helper(root);
11         return Math.max(res[0],res[1]);
12         
13 
14 
15     }
16 
17     public int[] helper(TreeNode r){
18         if(r == null) return new int[2];//边界条件,r为null时,跳出
19         int[] left = helper(r.left);//对于以r.left为根的树,计算抢劫根节点(r.left)与不抢劫根节点可获得最大金额. left[0]则为不抢r.lrft可获得的最大金额,left[1]则为抢劫r.left可获得的最大金额  以下right[] 分析同理
20         int[] right = helper(r.right);
21         int[] res = new int[2];
22         res[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);//计算不抢劫当前根节点可获得的最大金额(那么其左右子树可以随便抢)
23         res[1] = r.val + left[0] + right[0];//计算若抢劫根节点可获得的最大金额(此时,其左右子树的根节点不能被抢)
24         return res;
25     }
26 }

 

posted @ 2020-12-22 13:19  A_Aron  阅读(172)  评论(0)    收藏  举报
//一下两个链接最好自己保存下来,再上传到自己的博客园的“文件”选项中