🔥 个人主页: 黑洞晓威
😀你不必等到非常厉害,才敢开始,你需要开始,才会变的非常厉害
198.打家劫舍
力扣题目链接(opens new window)
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
- 示例 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
这道题目可以使用动态规划(Dynamic Programming, DP)的策略来解决。动态规划是一种用来解决需要考虑多个子问题的优化问题的方法,在此问题中,我们需要决定在每一步中是偷窃当前的房屋还是跳过,以便总金额最大化,同时不触发警报。
解题思路
- **状态定义:**设
dp[i]
表示到第i
个房屋时能偷窃到的最大金额,i
从0开始。 - **状态转移:**对于每个房屋
i
,小偷有两个选择:偷或不偷。- 如果小偷选择偷当前房屋
i
,那么他不能偷窃前一个房屋i-1
,所以dp[i] = nums[i] + dp[i-2]
。 - 如果小偷选择不偷当前房屋
i
,那么dp[i] = dp[i-1]
,因为他可以保持到i-1
为止的最大金额。 - 综上所述,我们可以得到状态转移方程:
dp[i] = max(dp[i-1], nums[i] + dp[i-2])
。
- 如果小偷选择偷当前房屋
- 初始条件:
- 当只有一个房屋时,偷这个房屋将获得最大收益,即
dp[0] = nums[0]
。 - 当有两个房屋时,选择金额较大的那个房屋,所以
dp[1] = max(nums[0], nums[1])
。
- 当只有一个房屋时,偷这个房屋将获得最大收益,即
- **返回值:**最终,
dp[n-1]
将表示偷窃前n
个房屋可以获得的最大金额,其中n
是输入数组nums
的长度。
代码实现
public class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int n = nums.length;
int[] dp = new int[n];
// 初始条件
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
// 从第三个房屋开始逐个考虑
for (int i = 2; i < n; i++) {
// 状态转移方程
dp[i] = Math.max(dp[i-1], nums[i] + dp[i-2]);
}
// 返回最后一个房屋的最大偷窃金额
return dp[n-1];
}
}
213.打家劫舍II
力扣题目链接(opens new window)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。
示例 1:
- 输入:nums = [2,3,2]
- 输出:3
- 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
- 示例 2:
- 输入:nums = [1,2,3,1]
- 输出:4
- 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
- 示例 3:
- 输入:nums = [0]
- 输出:0
解题思路
针对这个问题,我们可以采取的策略是根据打家劫舍I的解题思路,由于房屋是形成一个环的,所以有一个新的限制条件:不能同时偷第一个房屋和最后一个房屋。这意味着我们有两种情况需要考虑并取其中的最大值:
- 偷第一间到倒数第二间房屋。
- 偷第二间到最后一间房屋。
我们可以重用打家劫舍I的解决方案,将这两种情况都求一遍最大值,然后取两者中的较大值即可。具体实现时,我们可以定义一个辅助函数,用于计算在指定起始和结束位置的条件下能够偷窃到的最高金额。
代码实现
public class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int n = nums.length;
// 当房屋数量小于三时的特殊处理
if (n == 1) return nums[0];
if (n == 2) return Math.max(nums[0], nums[1]);
// 分两种情况,不偷第一间或不偷最后一间
return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));
}
// 辅助函数,实现打家劫舍I的逻辑,但是在指定的起始和结束位置
private int robRange(int[] nums, int start, int end) {
if (start == end) {
return nums[start];
}
int[] dp = new int[nums.length];
dp[start] = nums[start];
dp[start + 1] = Math.max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
dp[i] = Math.max(dp[i - 1], nums[i] + dp[i - 2]);
}
return dp[end];
}
}
337.打家劫舍 III
力扣题目链接(opens new window)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
解题思路
该问题可以通过树形动态规划(也称为后序遍历深度优先搜索)来解决。对于树中的任何一个节点,小偷有两个选择:偷或不偷。
- 如果偷当前节点,那么就不能偷其子节点;
- 如果不偷当前节点,那么可以选择偷取其子节点。
因此,对于每个节点,我们需要返回两个信息:偷该节点时的最大金额与不偷该节点时的最大金额。
- 对于树的每一个节点,返回一个大小为2的数组,其中
arr[0]
存放不偷该节点时的最大金额,arr[1]
存放偷该节点时的最大金额。 - 不偷当前节点时的最大金额等于左子节点偷或不偷的最大值加上右子节点偷或不偷的最大值。
- 偷当前节点时的最大金额等于当前节点的金额加上左子节点不偷时的金额加右子节点不偷时的金额。
- 使用后序遍历的方式遍历整棵树,依次求出每个节点偷与不偷时的最大金额。
- 根节点的最大金额即为答案。
代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public int rob(TreeNode root) {
int[] result = robSub(root);
return Math.max(result[0], result[1]);
}
private int[] robSub(TreeNode node) {
if (node == null) {
return new int[2];
}
int[] left = robSub(node.left);
int[] right = robSub(node.right);
// res[0]代表不偷该节点时的最大金额,res[1]代表偷该节点时的最大金额
int[] res = new int[2];
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = node.val + left[0] + right[0];
return res;
}
}