leetcode 198. 打家劫舍 easy
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
解题思路:
这道题的本质相当于在一列数组中取出一个或多个不相邻数,使其和最大。那么我们对于这类求极值的问题首先考虑动态规划DP来解,我们维护一个一位数组dp,其中dp[i]表示到i位置时不相邻数能形成的最大和
代码:
class Solution {
public:
int rob(vector<int>& nums) {
// dp【i】=max(dp【i-1】,dp【i-2】+nums【i】)
// dp【i】表示 nums【0~i】的最大偷法
if(nums.empty())
return 0;
if(nums.size()==1)
return nums[0];
vector<int> dp(nums.size());
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();++i){
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.size()-1];
}
};
leetcode 213. 打家劫舍 II medium
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
解题思路:
这道题是上一题的拓展,现在房子排成了一个圆圈,则如果抢了第一家,就不能抢最后一家,因为首尾相连了,所以第一家和最后一家只能抢其中的一家,或者都不抢,那我们这里变通一下,如果我们把第一家和最后一家分别去掉,各算一遍能抢的最大值,然后比较两个值取其中较大的一个即为所求。(虽然会两个会有重叠部分,但我们取的是max, 就没有问题)
代码:
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()<=1)
return nums.empty()?0:nums[0];
return max(helper(nums,0,nums.size()-2),helper(nums,1,nums.size()-1));
}
int helper(vector<int>& nums, int left,int right){
// 传进来的可能只有一个元素
if(left>=right)
return nums[left];
vector<int> dp(right-left+1);
dp[0]=nums[left];
dp[1]=max(nums[left],nums[left+1]);
for(int i=2;i<dp.size();++i){
dp[i]=max(dp[i-1],dp[i-2]+nums[left+i]);
}
return dp.back();
}
};
// 状态压缩
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty())
return 0;
if (nums.size() < 2)
return nums.front();
return max(dp(nums, 1, nums.size()-1), dp(nums, 0, nums.size()-2));
}
int dp(vector<int> &nums, int l, int r){
if (l==r)
return nums[l];
int pre = 0;
int cur = nums[l];
for (int i=l+1; i<=r;i++){
int tmp = cur;
cur = max(cur, pre+nums[i]);
pre = tmp;
}
return cur;
}
};
leetcode 337. 打家劫舍 III medium
题目描述:
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
解题思路:
这个题目就是求以root为根所能够偷到的最大值。
方法一:
采用树型dp的方式,自底向上递归,返回值是个pair, first是当前节点偷能偷到的最大值,second是当前节点不偷。
方法二:
是自己想的,没有上一个好,但是也复习了一下可以通过引用参数来返回值。
以root为根能够偷到的钱,分为两种情况,包括root,不包括root。
假如不包括root,那就是以root-》left为根偷到的最大值加上以root-》right为根偷到的最大值。
而包括root,那就是root-》val加上root的四个孙子
root.left.left, root.left.right, root.right.left, root.right.right
.用了个小技巧,其实跟判断是否平衡二叉树的那个技巧是一样的,就是每次遍历的时候,我返回的不仅仅一个值,我在遍历cur这个节点的时候,我除了return 以cur根的最大值之外,我还“返回”了分别以cur的两个儿子为根的最大值,只不过这两个"返回"是通过我两个引用参数“返回的”。这个知识点爷好像在哪本cpp书看过,orz,当时还没怎么当没事
代码:
方法一
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
/*
first: 选择了这个节点
second: 没选这个节点
*/
int rob(TreeNode* root) {
pair<int, int> ret = sovle(root);
return max(ret.first, ret.second);
}
pair<int, int> sovle(TreeNode *root){
if (!root)
return {0, 0};
pair<int, int> l = sovle(root->left);
pair<int, int> r = sovle(root->right);
return {root->val + l.second + r.second, max(l.first, l.second) + max(r.first, r.second)};
}
};
方法二
class Solution {
public:
int rob(TreeNode* root) {
int l,r;
return helper(root,l,r);
}
//以root为根所能偷到最多的钱,是包括根节点的最大值和不包括根节点的最大值的最大值
// l,r 分别用来保存以root->left为根能够偷到的最大值和以root->right为根能够偷到的最大值
int helper(TreeNode* root,int &l, int &r){
if(!root){
l=0; r=0;
return 0;
}
int ll,lr,rl,rr;
l=helper(root->left,ll,lr);
r=helper(root->right,rl,rr);
return max(root->val+ll+lr+rl+rr,l+r);
}
};