LeetCode Hot100(68/100)——198. 打家劫舍

题目链接

https://leetcode.cn/problems/house-robber/description/?envType=study-plan-v2&envId=top-100-liked


题目说明

你是一个专业小偷,计划偷窃沿街的房屋。每间房内有一定金额 nums[i]
相邻的房屋装有联动报警系统,如果同一晚偷了相邻两间房,就会触发报警。

请你返回在不触发报警的前提下,一晚能偷到的最高金额

示例

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

解题思路总览

打家劫舍

本质

线性DP

每间房只有"偷/不偷"两种决策

状态定义

dp[i] = 前 i 间房的最高金额

状态转移

不偷第 i 间: dp[i-1]

偷第 i 间: dp[i-2] + nums[i]

取最大值

可选解法

纯递归(指数级)

记忆化递归

DP数组(自底向上)

空间优化DP(滚动变量)


解法一:纯递归(回溯思想)

原理

对于第 i 间房,有两种选择:

  1. 不偷它:去看 i-1 的最优解
  2. 偷它:那 i-1 不能偷,只能加上 i-2 的最优解

递归式:
f ( i ) = max ⁡ ( f ( i − 1 ) , f ( i − 2 ) + n u m s [ i ] ) f(i) = \max(f(i-1), f(i-2) + nums[i]) f(i)=max(f(i1),f(i2)+nums[i])

Java 代码

class Solution {
    public int rob(int[] nums) {
        return dfs(nums, nums.length - 1);
    }

    private int dfs(int[] nums, int i) {
        if (i < 0) return 0;
        if (i == 0) return nums[0];
        return Math.max(dfs(nums, i - 1), dfs(nums, i - 2) + nums[i]);
    }
}

复杂度分析

  • 时间复杂度:O(2^n)(大量重复子问题)
  • 空间复杂度:O(n)(递归栈)

该方法直观,但会超时,不推荐实际提交。


解法二:记忆化递归(自顶向下 DP)

原理

在纯递归的基础上,加一个 memo[i] 保存 f(i),避免重复计算。

Java 代码

import java.util.Arrays;

class Solution {
    private int[] memo;

    public int rob(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo, -1);
        return dfs(nums, nums.length - 1);
    }

    private int dfs(int[] nums, int i) {
        if (i < 0) return 0;
        if (memo[i] != -1) return memo[i];
        int ans = Math.max(dfs(nums, i - 1), dfs(nums, i - 2) + nums[i]);
        memo[i] = ans;
        return ans;
    }
}

复杂度分析

  • 时间复杂度:O(n)(每个状态只算一次)
  • 空间复杂度:O(n)(memo + 递归栈)

解法三:动态规划数组(自底向上)

原理

定义 dp[i]:考虑前 i+1 间房(0~i)时的最高金额。
转移:

  • 不偷第 i 间:dp[i-1]
  • 偷第 i 间:dp[i-2] + nums[i]
  • 所以:
    dp[i] = max(dp[i-1], dp[i-2] + nums[i])

状态转移流程图

不偷

开始 i

是否偷第 i 间?

收益 = dp[i-1]

收益 = dp[i-2] + nums[i]

dp[i] = max(两者)

处理 i+1

Java 代码

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 1) return nums[0];

        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], dp[i - 2] + nums[i]);
        }

        return dp[n - 1];
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

解法四:空间优化 DP(推荐)

原理

观察到 dp[i] 只依赖 dp[i-1]dp[i-2],无需整个数组。
用两个变量滚动维护:

  • prev2 = dp[i-2]
  • prev1 = dp[i-1]

每次更新:

  • cur = max(prev1, prev2 + nums[i])
  • 然后 prev2 = prev1, prev1 = cur

时序图(变量更新)

cur prev1 prev2 Loop cur prev1 prev2 Loop cur = max(prev1, prev2 + nums[i]) prev2 <- prev1 prev1 <- cur

Java 代码

class Solution {
    public int rob(int[] nums) {
        int prev2 = 0; // dp[i-2]
        int prev1 = 0; // dp[i-1]

        for (int x : nums) {
            int cur = Math.max(prev1, prev2 + x);
            prev2 = prev1;
            prev1 = cur;
        }

        return prev1;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

示例推演(以 [2,7,9,3,1] 为例)

inums[i]选不偷(dp[i-1])选偷(dp[i-2]+nums[i])dp[i]
02--2
17277
2971111
33111011
41111212

最终答案:12


各解法实现复杂度与性能对比

解法核心思想时间复杂度空间复杂度实现复杂度性能表现适用场景
纯递归暴力枚举偷/不偷O(2^n)O(n)很差,易超时仅用于理解问题
记忆化递归递归 + 缓存子问题O(n)O(n)优秀喜欢递归写法时
DP数组自底向上填表O(n)O(n)优秀且稳定面试常规写法
空间优化DP滚动变量替代数组O(n)O(1)中偏低最优实战/提交推荐
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracyCoder123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值