198.打家劫舍

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

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

示例 1:

1
2
3
4
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

1
2
3
4
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

解题思路

这是一道经典的动态规划题目,考察对状态转移的理解。

核心思想

  • 使用动态规划,dp[i]表示偷到第i个房屋时的最大金额
  • 状态转移:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
  • 不能偷相邻的房屋,所以要么偷当前房屋,要么不偷

方法一:动态规划(推荐)

时间复杂度 O(n)、空间复杂度 O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @param {number[]} nums
* @return {number}
*/
var rob = function(nums) {
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];
if (nums.length === 2) return Math.max(nums[0], nums[1]);

const dp = new Array(nums.length);
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);

for (let i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
}

return dp[nums.length - 1];
};

方法二:空间优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var rob = function(nums) {
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];

let prev2 = nums[0];
let prev1 = Math.max(nums[0], nums[1]);

for (let i = 2; i < nums.length; i++) {
const current = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = current;
}

return prev1;
};

方法三:递归法(超时,但思路清晰)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var rob = function(nums) {
function robHelper(index) {
if (index >= nums.length) return 0;

// 选择偷当前房屋
const robCurrent = nums[index] + robHelper(index + 2);

// 选择不偷当前房屋
const skipCurrent = robHelper(index + 1);

return Math.max(robCurrent, skipCurrent);
}

return robHelper(0);
};

方法四:记忆化递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var rob = function(nums) {
const memo = new Map();

function robHelper(index) {
if (index >= nums.length) return 0;
if (memo.has(index)) return memo.get(index);

const robCurrent = nums[index] + robHelper(index + 2);
const skipCurrent = robHelper(index + 1);

const result = Math.max(robCurrent, skipCurrent);
memo.set(index, result);

return result;
}

return robHelper(0);
};

解题要点

  1. 动态规划

    • dp[i]表示偷到第i个房屋时的最大金额
    • 状态转移:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
    • 不能偷相邻的房屋
  2. 状态转移方程

    • 不偷当前房屋:dp[i] = dp[i-1]
    • 偷当前房屋:dp[i] = dp[i-2] + nums[i]
    • 取两者最大值
  3. 边界处理

    • 空数组:返回0
    • 一个房屋:返回该房屋金额
    • 两个房屋:返回较大值

面试要点

  • 高频考点:这道题是面试中的高频动态规划题
  • 状态定义:考察对状态转移的理解
  • 空间优化:了解如何优化空间复杂度
  • 递归思维:考察递归到动态规划的转换

相关题目

扩展思考

  1. 为什么使用动态规划?

    • 问题具有最优子结构
    • 当前状态依赖于前面的状态
    • 避免重复计算
  2. 时间复杂度分析

    • 动态规划:O(n)
    • 递归:O(2^n)
    • 空间复杂度:O(n) vs O(1)
  3. 状态转移的理解

    • dp[i-1]:不偷当前房屋的最大金额
    • dp[i-2] + nums[i]:偷当前房屋的最大金额
    • 两者取最大值
  4. 边界情况

    • 空数组:返回0
    • 单个房屋:返回该房屋金额
    • 两个房屋:返回较大值
  5. 实际应用

    • 资源分配问题
    • 投资组合优化
    • 任务调度
  6. 变种问题

    • 房屋围成环形
    • 二叉树结构
    • 多个小偷

最后更新: 2021-10-06