Leetcode_打家劫舍三道题(动态规划总结)
分类专栏: Leetcode # 动态规划 文章标签: 动态规划 leetcode 算法
LeetCode198 打家劫舍
leetCode213 打家劫舍2
LeetCode337 打家劫舍3
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400
思路:动态规划
分为以下四个步骤
- 定义子问题
- 写出子问题的递推关系
- 确定 DP 数组的计算顺序
- 空间优化(可选)
首先定义子问题,题目是求偷n间房间的最大值,分解成子问题即求k个房间的最大值f(k),又保证不能相邻,则递归分析规律,f(k)要么第k个不偷 要么偷第k个 不偷第k-1个
其动态转移方程就为f(k)=max(f(k-1),f(k-2)+nums[k-1])
int rob(vector<int>& nums) {
if (nums.size() == 0) {
return 0;
}
int N = nums.size();
vector<int> dp(N+1, 0);
dp[0] = 0;
dp[1] = nums[0];
for (int k = 2; k <= N; k++) {
dp[k] = max(dp[k-1], nums[k-1] + dp[k-2]);
}
return dp[N];
}
然后就是空间优化,很多时候我们并不需要始终持有全部的 DP 数组。对于小偷问题,我们发现,最后一步计算 f(n)f(n) 的时候,实际上只用到了 f(n-1)f(n−1) 和 f(n-2)f(n−2) 的结果。n-3n−3 之前的子问题,实际上早就已经用不到了。那么,我们可以只用两个变量保存两个子问题的结果,就可以依次计算出所有的子问题
int rob(vector<int>& nums) {
int prev = 0;
int curr = 0;
// 每次循环,计算“偷到当前房子为止的最大金额”
for (int i : nums) {
// 循环开始时,curr 表示 dp[k-1],prev 表示 dp[k-2]
// dp[k] = max{ dp[k-1], dp[k-2] + i }
int temp = max(curr, prev + i);
prev = curr;
curr = temp;
// 循环结束时,curr 表示 dp[k],prev 表示 dp[k-1]
}
return curr;
}
打家劫舍II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
思路:打家劫舍2 这次相比较于1,由直线变成了环,也就是说第一位和最后一位也算相邻的,所以就考虑三种情况,1.最后一个和第一个都没偷2.偷第一个没偷最后一个3.偷最后一个没偷第一个 而求最大值 第一种情况显然比后两种更小,所以只用考虑后两种求max 定义二维数组 dp[][2],当两次线性路动态规划求解,最后求出最大值
public int rob(int[] nums) {
// if(nums==null||nums.length==0){
// return 0;
// }
// if(nums.length==1){
// return nums[0];
// }
// int [][]dp=new int [nums.length][2]; //必偷第0家和不偷第0家两种情况分开
// dp[0][0]=0; //不要0 为0
// dp[1][0]=nums[1]; //不要0 前两个为Nums[1]
// dp[0][1]=nums[0]; //必要0 nums[0]
// dp[1][1]=nums[0]; //必要0 ,钱两个为nums[0]
// for(int i=2;i<nums.length;i++){
// if(i<nums.length-1){
// dp[i][0]=Math.max(dp[i-1][0], dp[i-2][0]+nums[i]);
// dp[i][1]=Math.max(dp[i-1][1], dp[i-2][1]+nums[i]);
// }else{
// dp[i][0]=Math.max(dp[i-1][0], dp[i-2][0]+nums[i]);
// dp[i][1]=dp[i-1][1];
// }
// }
// return Math.max(dp[nums.length-1][0], dp[nums.length-1][1]);
// }
java优化方案
class Solution {
public int rob(int[] nums) {
if(nums.length == 0) return 0;
if(nums.length == 1) return nums[0];
return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)),
myRob(Arrays.copyOfRange(nums, 1, nums.length)));
}
private int myRob(int[] nums) {
int pre = 0, cur = 0, tmp;
for(int num : nums) {
tmp = Math.max(pre + num, cur);
pre = cur;
cur=tmp;
}
return cur;
}
}
打家劫舍III
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
我们可以用 f(o) 表示选择 o 节点的情况下,o节点的子树上被选择的节点的最大权值和;g(o)表示不选择 o 节点的情况下,o节点的子树上被选择的节点的最大权值和;l和 r 代表 o 的左右孩子。
当 o 被选中时,o 的左右孩子都不能被选中,故 o 被选中情况下子树上被选中点的最大权值和为 l 和 r 不被选中的最大权值和相加,即 f(o) = g(l) + g®
当 o 不被选中时,o 的左右孩子可以被选中,也可以不被选中。对于 o 的某个具体的孩子 x,它对 o 的贡献是 x 被选中和不被选中情况下权值和的较大值。故 g(o) =max { f(l) , g(l)}+max{ f® , g®}
至此,我们可以用哈希映射来存 f 和 g 的函数值,用深度优先搜索的办法后序遍历这棵二叉树,我们就可以得到每一个节点的 f 和 g。根节点的 f 和 g 的最大值就是我们要找的答案。
class Solution {
public int rob(TreeNode root) {
int result[]=dfs(root);
return Math.max(result[0],result[1]);
}
public int[] dfs(TreeNode root) {
if(root==null){
return new int[]{0,0};
}
//每个节点都返回当前节点包含本节点的最大值,和不包含本节点的最大值
//结果数组,0不选本节点,1选择该节点
int left[]=dfs(root.left);
int right[]=dfs(root.right);
int noNodeResult=Math.max(left[0],left[1])+Math.max(right[0],right[1]);
int havaNodeResult=root.val+left[0]+right[0];
return new int []{noNodeResult,havaNodeResult};
}
}
// class Solution {
// Map<TreeNode,Integer> map1=new HashMap<TreeNode,Integer>(); //包含本几点的各个节点为根的子树的最大值
// Map<TreeNode,Integer> map2=new HashMap<TreeNode,Integer>(); //不包含本几点的各个节点为根的子树的最大值
// public int rob(TreeNode root) {
// map1.put(null, 0);
// map2.put(null, 0);
// if(root==null){
// return 0;
// }
// robDFS(root);
// return Math.max(map1.get(root), map2.get(root));
// }
// public void robDFS(TreeNode root){
// if(root==null){
// return;
// }
// robDFS(root.left);
// robDFS(root.right);
// //包含本节点的最大值=本节点值+不包含左子树根节点最大值+不包含右子树根节点最大值)
// int value1=map2.get(root.left)+ map2.get(root.right)+root.val;
// map1.put(root, value1);
// //不包含本节点的最大值=max(包含左子树根节点最大值,不包含子树根节点最大值)+max(包含右子树根节点最大值,不包含右子树根节点最大值)
// int value2=Math.max(map1.get(root.left),map2.get(root.left))+Math.max(map1.get(root.right),map2.get(root.right));
// map2.put(root, value2);
// }
// }