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 。

解题思路:

  本题实际就是典型的动态规划问题,沿街的房屋依次决定每一家偷还是不偷为一个阶段,用dp[i]表示前i家获取的最高金额,

第i阶段的决策就是两种:偷、不偷。则不难写出以下状态转移方程:dp[i]=max(dp[i-1],dp[i-2]+nums[i])

dp[i]表示前i家可以得到的最高金额,dp[i]=Max(dp[i-1],dp[i-2]+nums[i])

--- 从以上的递推公式可以看出,第i个状态只和i-1和i-2两个状态有关,因此,也可以省略掉数组,只维护两个变量即可。

 

 



class Solution {
public:
int rob(vector<int>& nums) {
int len = nums.size();
if(len==0){
return 0;
}
if(len==1){
return(nums[0]);
}
if(len==2){
return max(nums[0],nums[1]);
}
if(len==3){
return max((nums[0]+nums[2]),nums[1]);
}

vector<int> dp(len,0);
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
dp[2]=max((dp[0]+nums[2]),dp[1])

int res = dp[2];

for(int i=3;i<len;i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
res = max(res,dp[i]);
}

return res;
}
};




//解法二:动态规划(维护两个变量)
class Solution {
public:
int rob(vector<int>& nums) {
int len = nums.size();
if(len==0){
return 0;
}
if(len==1){
return(nums[0]);
}
if(len==2){
return max(nums[0],nums[1]);
}
if(len==3){
return max((nums[0]+nums[2]),nums[1]);
}

int first=0,second=0,res=0;

for(int i=0;i<len;i++){
res =max(first+nums[i],second);
first = second;
second = res;
}
return res;
}
};


  

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 。

 

思路:思路与上一题目类似,只是需要考虑从1到n-1家和从2到n家两种情况哪个更大即可

分两种情况计算:第一家抢,第一家不抢



class Solution {
public:
int rob(vector<int>& nums) {
int len = nums.size();
if(len==0){
return 0;
}
if(len==1){
return nums[0];
}
if(len==2){
return max(nums[0],nums[1]);
}

//第一家可抢
vector<int> dp1(len,0);
dp1[0]=nums[0];
dp1[1]=max(nums[0],nums[1]);
for(int i=2;i<len-1;i++){
dp1[i]=max(dp1[i-1],dp1[i-2]+nums[i]);
}

//第一家不可抢
vector<int> dp2(len,0);
dp2[0]=0;
dp2[1]=nums[1];
for(int i=2;i<len;i++){
dp2[i]=max(dp2[i-1],dp2[i-2]+nums[i]);
}

return max(dp1[len-2],dp2[len-1]);

}
};


//同样:维护两个变量,节省空间

class Solution {
public:
int rob(vector<int>& nums) {
int len = nums.size();
if(len==0){
return 0;
}
if(len==1){
return nums[0];
}
if(len==2){
return max(nums[0],nums[1]);
}

//第一家可抢
int first=nums[0];
int second=max(nums[0],nums[1]);
int res1=second;
for(int i=2;i<len-1;i++){
res1=max(second,first+nums[i]);
first = second;
second = res1;
}

//第一家不可抢
first=0;
second=nums[1];
int res2 = second;
for(int i=2;i<len;i++){
res2=max(second,first+nums[i]);
first = second;
second = res2;
}

return max(res1,res2);

}
};


  

 

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.

思路:和上两题不同的是:本题是二叉树形状,树中直接相连(也就是父子节点)不能同时打劫。

实际上,有上一题的经验,我们不难找到规律:这里可以按根结点是否打劫分为两种情况,如果根结点被打劫,

那么意味着它的子节点都不能打劫,需要直接打劫其孙子辈节点;如果根结点不打劫,那么可以考虑其左右孩子。

同时,由于树的特性,左右子树都是子问题,因此实际上就是两种情况分别递归。

分两种情况,根节点被打劫或者不被打劫,然后利用树的递归性质求解;

 

//这种解法超时,因为有递归,必然有许多重复计算,因为返回值中没有存储当前节点抢还是不抢。

 



/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/


class Solution {
public:
int rob(TreeNode* root) {
if(root==NULL){
return 0;
}

//打劫根节点
int res1 = 0;
res1 = res1+root->val;
if(root->left!=NULL){
res1=res1+rob(root->left->left)+rob(root->left->right);
}
if(root->right!=NULL){
res1=res1+rob(root->right->left)+rob(root->right->right);
}

//不打劫根节点
int res2=0;

res2=rob(root->left)+rob(root->right);

return max(res1,res2);

}
};


  

第二种方法:动态方程:每个节点两个选择:

(1)偷——两个子结点不能偷,能得到的最多钱存在steal[0]里面

(2)不偷——两个子结点需要比自己偷有更多的钱,存在steal[1]里面

用一个数组steal来存储是否偷窃的钱

那么能偷到的最多钱:

(1)不偷时,=左右子能偷到的钱之和

(2)偷时,=左孩子不偷时的钱+右孩子不偷时的钱+当前节点的钱

 



/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root)
{
auto ret = dfs(root);
return max(ret[0], ret[1]);
}

vector<int> dfs(TreeNode* root)
{
if (!root) return {0 ,0};
auto ret1 = dfs(root->left);
auto ret2 = dfs(root->right);
return {root->val + ret1[1] + ret2[1], max(ret1[0], ret1[1]) + max(ret2[0], ret2[1])};
}
};


  

另一种思路:动态规划

后序遍历二叉树,其中递归函数返回一个数组dp,有两个元素,dp[0]表示盗取当前遍历结点的最大金额,dp[1]表示不盗取当前遍历结点的最大金额。

因为,盗取当前结点,就不能盗取与它相连的结点,主要是其直接左、右孩子。故dp[0] = root->val + left[1] + right[1];

不盗取本结点,那么情况就有四种了,它的直接左、右孩子都盗取,直接左、右孩子只盗取1个,左、右孩子都不盗取,

即dp[1] = max(left[1]+right[0],max(left[0]+right[1],max(left[0]+right[0],left[1]+right[1])))




class Solution {
public:
vector<int> dp(TreeNode* root){
if(root == NULL) return {0,0};
vector<int> left = dp(root->left);
vector<int> right = dp(root->right);
vector<int> tmp(2,0);
tmp[0] = root->val + left[1] + right[1]; //盗取本节点能获得的最大值
tmp[1] = max(left[1]+right[0],max(left[0]+right[1],max(left[0]+right[0],left[1]+right[1]))); //不盗取本节点能获取的最大值
return tmp;
}
int rob(TreeNode* root) {
vector<int> ans = dp(root);
return max(ans[0],ans[1]);
}
};