LC 198. 打家劫舍

题目描述

这是 LeetCode 上的 198. 打家劫舍 ,难度为 中等

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

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

示例 1:

1
2
3
4
5
6
输入:[1,2,3,1]

输出:4

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

示例 2:
1
2
3
4
5
6
输入:[2,7,9,3,1]

输出:12

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

提示:

  • $1 <= nums.length <= 100$
  • $0 <= nums[i] <= 400$

状态机 DP

这是一道入门级的状态机线性 DP 题。

定义 $f[i][j]$ 为考虑前 $i$ 间房子,且第 $i$ 间房子的状态为 $j$ 时所能取得的最大价值(其中 $j = 0$ 代表不偷该房子,$j = 1$ 代表偷该房子)。

再结合题意,因为相邻房子不能同时被偷,可推导出状态转移方程为:

  • 当前房子不偷,则对前一间房子的状态无要求,状态值为前一状态的较大值:$f[i][0] = \max(f[i - 1][0], f[i - 1][1])$
  • 当前房子偷,此时限定了前一间只能不偷,状态值为前一间房子不偷时的最大价值,加上当前房子的价值: $f[i][1] = f[i - 1][0] + nums[i - 1]$

最终 $\max(f[n][0], f[n][1])$ 即是答案。

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int rob(int[] nums) {
int n = nums.length;
int[][] f = new int[n + 10][2];
for (int i = 1; i <= n; i++) {
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + nums[i - 1];
}
return Math.max(f[n][0], f[n][1]);
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
int f[n + 10][2];
f[0][0] = f[0][1] = 0;
for (int i = 1; i <= n; i++) {
f[i][0] = max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + nums[i - 1];
}
return max(f[n][0], f[n][1]);
}
};

TypeScript 代码:
1
2
3
4
5
6
7
8
9
10
11
function rob(nums: number[]): number {
const n = nums.length
const f = new Array<Array<number>>(n + 10)
f[0] = new Array<number>(2).fill(0)
for (let i = 1; i <= n; i++) {
f[i] = new Array<number>(2).fill(0)
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1])
f[i][1] = f[i - 1][0] + nums[i - 1]
}
return Math.max(f[n][0], f[n][1])
}

Python 代码:
1
2
3
4
5
6
7
8
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
f = [[0] * 2 for _ in range(n + 10)]
for i in range(1, n + 1):
f[i][0] = max(f[i - 1][0], f[i - 1][1])
f[i][1] = f[i - 1][0] + nums[i - 1]
return max(f[n][0], f[n][1])

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

优化

利用 $f[i][X]$ 仅依赖于 $f[i - 1][X]$,我们可以通过「滚动数组」的方式将空间优化至 $O(1)$。

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public int rob(int[] nums) {
int n = nums.length;
int[][] f = new int[][]{{0, 0}, {0, 0}};
for (int i = 1; i <= n; i++) {
int a = (i - 1) & 1, b = i & 1;
f[b][0] = Math.max(f[a][0], f[a][1]);
f[b][1] = f[a][0] + nums[i - 1];
}
return Math.max(f[n & 1][0], f[n & 1][1]);
}
}

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
int f[][2] = {{0, 0}, {0, 0}};
for (int i = 1; i <= n; i++) {
int a = (i - 1) & 1, b = i & 1;
f[b][0] = max(f[a][0], f[a][1]);
f[b][1] = f[a][0] + nums[i - 1];
}
return max(f[n & 1][0], f[n & 1][1]);
}
};

TypeScript 代码:
1
2
3
4
5
6
7
8
9
10
function rob(nums: number[]): number {
const n = nums.length
const f = [[0, 0], [0, 0]]
for (let i = 1; i <= n; i++) {
const a = (i - 1) & 1, b = i & 1
f[b][0] = Math.max(f[a][0], f[a][1])
f[b][1] = f[a][0] + nums[i - 1]
}
return Math.max(f[n & 1][0], f[n & 1][1])
}

Python 代码:
1
2
3
4
5
6
7
8
9
class Solution:
def rob(self, nums: List[int]) -> int:
n = len(nums)
f = [[0, 0], [0, 0]]
for i in range(1, n + 1):
a, b = (i - 1) & 1, i & 1
f[b][0] = max(f[a][0], f[a][1])
f[b][1] = f[a][0] + nums[i - 1]
return max(f[n & 1][0], f[n & 1][1])

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

最后

这是我们「刷穿 LeetCode」系列文章的第 No.198 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。