目录

解法1:记忆回溯

代码实现

解法2:动态规划

代码实现


题目链接

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

  • 示例 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

解法1:记忆回溯

这个题我们第一眼看去,感觉回溯法可以做,每一次都遍历后面第二个数据。但是这样的后果就是会超时,所以想到使用记忆法来记住已经遍历过的位置的最大值,后面直接返回下一个位置的最大值。

代码实现
int max_sum = 0;
int[] memo;
public int rob(int[] nums) {
    if (nums.length == 1) return nums[0];
    if (nums.length > 3 && nums[0] == 0 && nums[1] == 0&& nums[2] == 0) return 0;
    memo = new int[nums.length];
    backTracking(0, nums);
    return max_sum;
}
​
​
int backTracking(int index, int[] nums) {
    // 超过了nums的大小就返回0
    if (index >= nums.length) {
        return 0;
    }
    // 等于nums的大小就返回最后的值
    if (index == nums.length-1) {
        return nums[nums.length-1];
    }
    // 如果已经递归过的位置则直接返回值
    if (memo[index] != 0) {
        return memo[index];
    }
​
    int next_max =  0;
    for (int i = index; i < nums.length; i++) {
        int sum = backTracking(i + 2, nums) + nums[i];
        next_max = Math.max(next_max, sum);
    }
    memo[index] = next_max;
    max_sum = Math.max(next_max, max_sum);
​
    return memo[index];
}
​

解法2:动态规划

仔细一想,当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了。

所以这里就更感觉到,当前状态和前面状态会有一种依赖关系,那么这种依赖关系都是动规的递推公式。

当然以上是大概思路,打家劫舍是dp解决的经典问题,接下来我们来动规分析如下:

  1. 确定dp数组(dp table)以及下标的含义

dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]

  1. 定递推公式

决定dp[i]的因素就是第i房间偷还是不偷。

如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。

如果不偷第i房间,那么dp[i] = dp[i - 1],即考 虑i-1房,(注意这里是考虑,并不是一定要偷i-1房,这是很多同学容易混淆的点

然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

  1. dp数组如何初始化

从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]

从dp[i]的定义上来讲,dp[0] 一定是0,dp[1]就是nums[0]

代码如下:

int[] dp = new int[nums.length]
dp[0] = 0;
dp[1] = nums[0]
  1. 确定遍历顺序

dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历!

代码如下:

for (int i = 2; i < nums.size(); i++) {
    dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
代码实现
class Solution {
    public int rob(int[] nums) {
        int len = nums.length;
        if (len == 1) return nums[0];
​
        int[] dp = new int[len+1];
        dp[1] = nums[0];
​
        for (int i = 2; i <= len; i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i-1]);
        }
​
        return dp[len];
    }
}
02-22 19:06