动态规划由简及繁(二)
题目
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
balabala…。也就是说 找数组最大和,但是如果累加i,就不能累加i-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 。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 400
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber
著作权归领扣网络所有。
分析
-
问题分解
最后一步:假设数组长度为k,那么整个数组累加的和最大值,就和k-1以及k-2有关系,因为累加k就不能累加k-1了,只能累加k-2,如果不累加k,那么可以累加k-1。
-
状态
根据分解的问题,那么可以把状态确定为
设dp[i]为数组从0直到i所能累加的最大和
-
转移方程
max(dp[i - 2] + nums[i], dp[i - 1]), i >= 2 dp[0] , i = 0 max(dp[0], dp[1]) ,i = 1
-
编码
class Solution { public int rob(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1) { return nums[0]; } int[] dp = new int[nums.length]; dp[0] = nums[0]; dp[1] = Math.max(dp[0], nums[1]); for (int i = 2; i < nums.length; i++) { dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); } return dp[nums.length - 1]; } }
-
优化
每次只需要保存直到上上一个的最大值和上一个的最大值即可
class Solution { public int rob(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1) { return nums[0]; } int prePreMax = nums[0]; int preMax = Math.max(nums[0], nums[1]); for (int i = 2; i < nums.length; i++) { int curMax = Math.max(nums[i] + prePreMax, preMax); prePreMax = preMax; preMax = curMax; } return preMax; } }
题目升级
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:
输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/house-robber-ii
著作权归领扣网络所有。
分析
感觉和上一个差不多,区别在于首尾不能连着选。
-
问题分解
最后一步,
M(假设数组长度为k,那么整个数组累加的和最大值,就和k-1以及k-2有关系,因为累加k就不能累加k-1了,只能累加k-2,如果不累加k,那么可以累加k-1)。
M表示上面这个括号括起来的步骤
本题的最大值,因为第一个不能和最后一个一同选择,所以最大值,要么,是M(1…k个数组),要么是M(0…k-1)。
取最大就行了
-
状态
dp[i] = 前i个数组累加和最大值
-
状态转移方程相同(应该相同吧0.0)
-
编码
class Solution { public int rob(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1) { return nums[0]; } int len = nums.length; // 从0...n,最后取dp1[len - 2]即可,相当于抛弃最后一个不选 int[] dp1 = new int[len]; // 从1...n。相当于抛弃第一个不选 int[] dp2 = new int[len]; dp1[0] = nums[0]; dp1[1] = Math.max(nums[0], nums[1]); dp2[0] = 0; dp2[1] = nums[1]; for (int i = 2; i < len; i++) { dp1[i] = Math.max(dp1[i - 2] + nums[i], dp1[i - 1]); dp2[i] = Math.max(dp2[i - 2] + nums[i], dp2[i - 1]); } // 0...n抛弃第一个,1...n因为直接把dp2[0]初始化为0,已经抛弃了。二者取最大即可 return Math.max(dp1[len - 2], dp2[len - 1]); } }
-
优化空间
class Solution { public int rob(int[] nums) { if (nums.length == 0) { return 0; } if (nums.length == 1) { return nums[0]; } // 从0...n-1部分 int prePreMaxA = nums[0]; int preMaxA = Math.max(nums[0], nums[1]); // 从1...n部分 int prePreMaxB = 0; int preMaxB = nums[1]; int len = nums.length; int MaxA = getMax(nums, prePreMaxA, preMaxA, len - 1); int MaxB = getMax(nums, prePreMaxB, preMaxB, len); return Math.max(MaxA, MaxB); } private int getMax(int[] nums, int prePreMax, int preMax, int len) { for (int i = 2; i < len; i++) { int max = Math.max(prePreMax + nums[i], preMax); prePreMax = preMax; preMax = max; } return preMax; } }