打家劫舍
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 。
2.思路
2.1 第一眼看到这个问题,就会考虑到,每个房间都有选和不选两种状态,暴力至上,穷举所有的状态,并加入互不相邻的限制条件,然后求所有方案的值,并选出最大值,时间复杂度为 2^n.
2.2 题目要求取最大的抢劫方案,考虑使用贪心算法,依次选取最大价值的财物,但是由于限制不能相邻,不便使用该方法。
2.3 考虑使用动态规划。弄清楚以下问题:
原问题:求取 n 个房间的最佳抢劫方案;
子问题:求取前 i 个房间的最佳抢劫方案;
状态 :对前 i个房间,可以选择抢 第 i 个房间和 i -2之前的房间,或者不抢它,抢前 i -1 个房间;
边界条件:dp[0] = nums[0];dp[1] = max(nums[0],nums[1]);
状态转移方程:
每个房间有选和不选两个状态,考虑前 i 个房间的方案,如果选择了第 i 个房间,则一定不能选第 i - 1 个房间,那么最佳方案转化为求 前 i - 2个房间的最佳方案加上 nums[i];如果不选第i个房间,那么就可以选第 i - 1个房间的方案,即 dp[i] = Math.Max(dp[i-2]+nums[i],dp[i-1]),时间复杂度为O(n).
3.解法
//0ms,100%
public int rob(int[] nums) {
if(nums==null||nums.length==0)return 0;
int roomNums = nums.length;
if(roomNums==1) return nums[0];
if(roomNums==2)return Math.max(nums[0], nums[1]);
int[] dpResult = new int[roomNums];
dpResult[0] = nums[0];
dpResult[1] = Math.max(nums[0], nums[1]);
for(int i = 2;i< roomNums;i++) {
dpResult[i] = Math.max( dpResult[i-2]+nums[i], dpResult[i-1]); // 选择第i个房间,那么只能选第i-2个房间之前的,或者不选房间i,那么结果就是选i-1的方案
}
return dpResult[roomNums-1];
}
以上方法还可以对空间利用进行优化,不必拿一个数组专门存数据,充分利用中间结果赋值(来自leecode官方解法):
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int length = nums.length;
if (length == 1) {
return nums[0];
}
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < length; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}