Bootstrap

经典动态规划问题——打家劫舍,详细图解+逐步优化!

📖 问题描述

你是一个职业小偷,计划偷窃沿街的房屋。每间房内有一定现金,但相邻房屋装有防盗系统,如果两间相邻的房屋在同一晚被闯入,系统会自动报警。给定一个非负整数数组表示每个房屋的金额,计算在不触发警报的情况下,一夜能偷到的最高金额。


🌰 示例分析

示例1:
输入:[1,2,3,1]
输出:4
解释:偷第1号房(金额=1)和第3号房(金额=3),总计4。

示例2:
输入:[2,7,9,3,1]
输出:12
解释:偷第1号(2)、第3号(9)和第5号(1),总计12。


🧠 解题思路(动态规划四步法)
1. 定义状态

假设我们偷到第 i 个房屋时,能获得的最大金额为 dp[i]。此时有两种选择:

  • 不偷第i间:最大金额等于偷到前 i-1 间的最大值,即 dp[i-1]

  • 偷第i间:因为不能偷相邻的,最大金额等于偷到前 i-2 间的最大值 + 第i间的金额,即 dp[i-2] + nums[i]

取这两种选择的较大值,得到状态转移方程:

dp[i] = max(dp[i-1], dp[i-2] + nums[i])
2. 初始化
  • 只有1间房时,必须偷:dp[0] = nums[0]

  • 有2间房时,偷金额较大的那间:dp[1] = max(nums[0], nums[1])

3. 填表顺序

从第3间房开始,逐步计算到最后一间。

4. 空间优化

观察状态转移方程,发现 dp[i] 只依赖前两个状态。因此只需用两个变量滚动更新,无需存储整个数组,空间复杂度从 O(n) 降为 O(1)


🖥️ 代码实现
方法一:动态规划(空间优化版)
class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 0) return 0;
        if (n == 1) return nums[0];
        
        int prev = nums[0];             // 代表 dp[i-2]
        int curr = Math.max(nums[0], nums[1]); // 代表 dp[i-1]
        
        for (int i = 2; i < n; i++) {
            int temp = curr;
            curr = Math.max(curr, prev + nums[i]);
            prev = temp;
        }
        return curr;
    }
}
方法二:记忆化搜索(递归+备忘录)
class Solution {
    private int[] nums, memo;

    public int rob(int[] nums) {
        this.nums = nums;
        int n = nums.length;
        memo = new int[n];
        Arrays.fill(memo, -1); // 初始化备忘录
        return dfs(n - 1);     // 从最后一间房开始决策
    }

    // 定义:偷到第i间房时的最大金额
    private int dfs(int i) {
        if (i < 0) return 0;          // 没有房屋可偷
        if (memo[i] != -1) return memo[i]; // 查备忘录
        
        // 选择偷或不偷第i间房,取较大值
        int res = Math.max(dfs(i - 1), dfs(i - 2) + nums[i]);
        memo[i] = res; // 存备忘录
        return res;
    }
}

📊 复杂度分析
  • 时间复杂度O(n),每个房屋只需计算一次。

  • 空间复杂度

    • 动态规划(优化后):O(1)

    • 记忆化搜索:O(n)(递归栈深度 + 备忘录)


💡 总结与思考
  1. 动态规划核心:通过子问题的最优解推导全局最优,避免重复计算。

  2. 适用场景:问题具有重叠子问题和最优子结构时。

  3. 举一反三:若房屋成环形(首尾相连),如何解决?不妨拆分为两个线性问题:不偷第一间或不偷最后一间,取较大值。

练习推荐:LeetCode 213(打家劫舍II)、337(二叉树形式打家劫舍)。

;