有一个聪明的小偷计划偷窃藏在房屋中的现金,仅当相邻的房屋同一晚上失窃时才触发报警装置,问在不触发报警情况下,小偷一个晚上能够偷窃的最大现金数量。
房屋的排列方式各式各样,分别如下:
1,线性排列
来源:198. 打家劫舍
解题思路:动态规划
拆解子问题,dp[i]定义为偷窃前i个房屋的最大金额,那么
- 如果偷窃nums[i-1],那么nums[i]不能偷窃,则dp[i]=dp[i-1];
- 如果没有偷窃nums[i-1],那么nums[i]可偷窃,则dp[i]=nums[i] + dp[i-2];
综合二者取最大,得dp[i] = max{dp[i-1], dp[i-2]+nums[i]}。
class Solution {
public:
int rob(vector<int>& nums) {
// 为了处理前2房屋,定义dp比nums长度多1,dp[i+1]对应nums[i]
vector<int> dp(nums.size() + 1);
dp[0] = 0;
dp[1] = nums[0];
for (int i = 1; i < nums.size(); i++) {
dp[i+1] = max(dp[i], dp[i-1] + nums[i]);
}
return dp[nums.size()];
}
};
空间优化:
class Solution {
public:
int rob(vector<int>& nums) {
int one = 0, two = nums[0];
for (int i = 1; i < nums.size(); i++) {
int three = max(two, one + nums[i]);
one = two;
two = three;
}
return two;
}
};
2,围成一圈
来源:213. 打家劫舍 II
解题思路:
去掉其中一个房屋,将其变成线性的,如下图,
去掉0号房屋则为:
去掉11号房屋则为:
其他房屋也可以去掉,但是从代码角度来看,房屋的存储用的是一位数组,首位2个元素去掉后其余元素依然连续,方便处理。
class Solution {
public:
int robRange(const vector<int>& nums, int start, int end) {
int one = 0, two = nums[start];
for (int i = start+1; i < end; i++) {
int three = max(two, one + nums[i]);
one = two;
two = three;
}
return two;
}
int rob(vector<int>& nums) {
int length = nums.size();
if (length == 1) {
return nums[0];
} else if (length == 2) {
return max(nums[0], nums[1]);
}
return max(robRange(nums, 0, length - 1), robRange(nums, 1, length));
}
};
3,二叉树型
解题思路:自顶向下递归
分成两种情况讨论:
- 抢劫根节点:最大金额 = 根节点 + 左子树中不抢根节点的最大金额 + 右子树中不抢根节点的最大金额
- 不抢根节点:最大金额 = 左子树抢与不抢根节点最大金额 + 右子树抢与不抢根节点最大金额
class Solution {
public:
int rob(TreeNode* root) {
int max1, max2;
rob(root, &max1, &max2);
return max(max1, max2);
}
// max1:抢劫根节点,max2:不抢根节点
void rob(TreeNode* root, int *max1, int *max2) {
if (root == NULL) {
*max1 = 0;
*max2 = 0;
return;
}
int left_max1, left_max2;
rob(root->left, &left_max1, &left_max2);
int right_max1, right_max2;
rob(root->right, &right_max1, &right_max2);
*max1 = root->val + left_max2 + right_max2; // 抢劫根节点
*max2 = max(left_max1, left_max2) + max(right_max1, right_max2); // 不抢根节点
}
};