描述
你是一个经验丰富的小偷,准备偷沿街的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家;如果偷了第二家,那么就不能偷第一家和第三家。
给定一个整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。
数据范围:数组长度满足 1≤n≤2×105 1 \le n \le 2\times 10^5\ 1≤n≤2×105 ,数组中每个值满足 1≤num[i]≤5000 1 \le num[i] \le 5000 \ 1≤num[i]≤5000
示例1
输入:[1,2,3,4]
返回值: 6
说明: 最优方案是偷第 2,4 个房间
示例2
输入: [1,3,6]
返回值:7
说明:最优方案是偷第 1,3个房间
示例3
输入: [2,10,5]
返回值: 10
说明: 最优方案是偷第 2 个房间
思路:
或许有人认为利用贪心思想,偷取最多人家的钱就可以了,要么偶数家要么奇数家全部的钱,但是有时候会为了偷取更多的钱,或许可能会连续放弃两家不偷,因此这种方案行不通,我们依旧考虑动态规划。
具体做法:
step 1:用dp[i]表示长度为i的数组,最多能偷取到多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱。
step 2:(初始状态) 如果数组长度为1,只有一家人,肯定是把这家人偷了,收益最大,因此dp[1]=nums[0]dp[1] = nums[0]dp[1]=nums[0]。
step 3:(状态转移) 每次对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那么前一家必定不能偷,因此累加的上上级的最多收益,同理如果选择不偷他,那我们最多可以累加上一级的收益。因此转移方程为dp[i]=max(dp[i−1],nums[i−1]+dp[i−2])dp[i] = max(dp[i - 1], nums[i - 1] + dp[i - 2])dp[i]=max(dp[i−1],nums[i−1]+dp[i−2])。这里的i在dp中为数组长度,在nums中为下标。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int rob (int[] nums) {
// write code here
//1.dp[i]表示长度为i的数组,最多能偷取多少钱(用dp[i]一维整型数组表示长度为i的数组,最多能偷取到多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱)
int[] dp=new int[nums.length+1];
//2.长度为1只能偷第一家(初始状态)如果数组长度为1,只有一家人肯定是把这家人偷了,利益最大,即dp[1]=nums[0]的情况。
dp[1]=nums[0];
//3.排除只有一家的情况外,根据整型数组nums的长度,遍历
for(int i=2;i<=nums.length;i++)
//4.对于每家可以选择偷或者不偷(状态转移)每次对于一个人家,我们选择偷他或者不偷它,如果我们偷那么前一家必定不能偷,因此累加的上上级的最多手机,同理如果选择不偷他,那我们最多可以累加上一级的收益。[简而言之,如果偷的话收益就是当前加的现金数加上上家之前所有偷的现金数的累加和。如果不偷的或就是不算上当前家的现金数,即为上家之前所有偷的现金的数]
dp[i]=Math.max(dp[i-1],nums[i-1]+dp[i-2]);
//5.最后返回当遍历完成后所偷的金钱数。
return dp[nums.length];
}
}
打家劫舍(二)
描述
你是一个经验丰富的小偷,准备偷沿湖的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻。
给定一个长度为n的整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。
数据范围:数组长度满足 1≤n≤2×105 1 \le n \le 2\times10^5 \ 1≤n≤2×105 ,数组中每个值满足 1≤nums[i]≤5000 1 \le nums[i] \le 5000 \ 1≤nums[i]≤5000
示例1
输入:[1,2,3,4]
返回值: 6
说明:最优方案是偷第 2 4 个房间
示例2
输入: [1,3,6]
返回值: 6
说明:由于 1 和 3 是相邻的,因此最优方案是偷第 3 个房间
思路:
这道题与BM78.打家劫舍(一)比较类似,区别在于这道题是环形,第一家和最后一家是相邻的,既然如此,在原先的方案中第一家和最后一家不能同时取到。
具体做法:
step 1:使用原先的方案是:用dp[i]表示长度为i的数组,最多能偷取到多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱。
step 2:(初始状态) 如果数组长度为1,只有一家人,肯定是把这家人偷了,收益最大,因此dp[1]=nums[0]dp[1] = nums[0]dp[1]=nums[0]。
step 3:(状态转移) 每次对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那么前一家必定不能偷,因此累加的上上级的最多收益,同理如果选择不偷他,那我们最多可以累加上一级的收益。因此转移方程为dp[i]=max(dp[i−1],nums[i−1]+dp[i−2])dp[i] = max(dp[i - 1], nums[i - 1] + dp[i - 2])dp[i]=max(dp[i−1],nums[i−1]+dp[i−2])。这里的i在dp中为数组长度,在nums中为下标。
step 4:此时第一家与最后一家不能同时取到,那么我们可以分成两种情况讨论:
情况1:偷第一家的钱,不偷最后一家的钱。初始状态与状态转移不变,只是遍历的时候数组最后一位不去遍历。
情况2:偷最后一家的请,不偷第一家的钱。初始状态就设定了dp[1]=0dp[1]=0dp[1]=0,第一家就不要了,然后遍历的时候也会遍历到数组最后一位。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型一维数组
* @return int整型
*/
public int rob (int[] nums) {
// write code here
//1.dp[i]表示长度为i的数组,最多能偷取多少钱(用dp[i]表示长度为i的数组,最多能偷取多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱)
int[] dp = new int[nums.length + 1];
//2.(初始状态)如果数组长度为1,只有一家人,肯定是把中家人偷了,收益最大,因此dp[i]=nums[0]-----选择偷第一家(Step 4总的情况1:偷第一家的的钱)
dp[1] = nums[0];
//3.排除第一家后,根据for循环遍历其余家是否可以偷,当然选择偷了第一家,最后一家不能偷(Step 4中情况1:初始状态和转移转台不变,知识遍历数组最后一位不取遍历)
for (int i = 2; i < nums.length; i++)
//4.对于每家可以选择偷或者不偷(状态转移)每次对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那前一家必定能能偷,因此累加的上上级的最多收益,同理如果选择不偷他,那我们最多可以累加上一级收益。这里的i在dp中为数组的长度,在nums中为下标。
dp[i] = Math.max(dp[i - 1], nums[i - 1] + dp[i - 2]);
//5.将dp[]数组的结果赋值给整型res
int res = dp[nums.length - 1];
//6.清除dp数组,第二循环
Arrays.fill(dp, 0);
//7.不偷第一家(step 4 中情况2:偷最后一家的钱,不偷最后一家的钱。即为初始状态就设定dp[1]=0,第一家就不要了)
dp[1] = 0;
//8.当不同最后一家,可以偷最后一家,然后for循环遍历,对于每家可以选择偷不偷(当偷最后一家是遍历的时候也会遍历数组的最后一位)
for (int i = 2; i <= nums.length; i++)
//9.对于每家可以选择偷或者不偷
dp[i] = Math.max(dp[i - 1], nums[i - 1] + dp[i - 2]);
//10.选择最大值
return Math.max(res, dp[nums.length]);
}
}