- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前言
打家劫舍一共两道题目,算是进阶吧。
打家劫舍1
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 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 。
提示: 1 <= nums.length <= 100 0 <= nums[i] <= 400
思路
该题一看就是一个动态规划的问题。 我们定义一个数组,假定小偷就是从 1号开始,每经过一个房间就有两种选择,偷或者不偷。使用贪心的原则,即我们只考虑当前到达的房间 能够达到的最好的收益。而如何让我们当前房屋取得的收益是能达到的最高呢?我们需要比较之前所有可以偷取的房间的值,取其最大值。
代码
public class Rob {
public int rob(int[] nums) {
int[] dp = new int[nums.length];
int max = -1;
if (nums.length == 1) return nums[0];
else if (nums.length == 2) return Math.max(nums[0],nums[1]);
dp[0] = nums[0];//偷
dp[1] = nums[1];
max = Math.max(nums[0],nums[1]);
for(int i=1;i<dp.length;i++)
for (int j = i - 2;j>=0;j--){
dp[i] = Math.max(dp[j] + nums[i],dp[i]);
max = Math.max(dp[i],max);
}
return max;
}
}
打家劫舍2
在打家劫舍1的基础上,增加了一个条件:这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的. 示例 1:
输入: nums = [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
思路
这道题如果单独出现,围城一圈这个条件可能还会产生一些干扰,但是在 题目1的基础上,其实就很容易想到。我们可以针对1号房屋偷与不偷 两种情况处理下,其他房屋均按照问题1方式解决。
-
1号房屋偷,那么最后房屋就必定不能偷
-
1号房屋不偷,最后房屋可偷可不偷
代码
public int rob(int[] nums) {
int[] dp = new int[nums.length];
int max = -1;
if (nums.length == 1) return nums[0];
else if (nums.length == 2) return Math.max(nums[0], nums[1]);
else if (nums.length == 3) return Math.max(nums[2], Math.max(nums[0], nums[1]));
// 假定偷头部
dp[0] = nums[0];//偷
dp[1] = nums[1];
max = Math.max(nums[0], nums[1]);
for (int i = 2; i < dp.length - 1; i++)
for (int j = i - 2; j >= 0; j--) {
dp[i] = Math.max(dp[j] + nums[i], dp[i]);
max = Math.max(dp[i], max);
}
// 假定不偷头部
for (int i = 0; i < dp.length; i++) dp[i] = 0;
dp[1] = nums[1];
for (int i = 2; i < dp.length; i++)
for (int j = i - 2; j >= 0; j--) {
dp[i] = Math.max(dp[j] + nums[i], dp[i]);
max = Math.max(dp[i], max);
}
return max;
}