LC-6346.打家劫舍IV

题目描述

leetcode 中等题

沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。

由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。

小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。

给你一个整数数组 nums 表示每间房屋存放的现金金额。形式上,从左起第 i 间房屋中放有 nums[i] 美元。

另给你一个整数数组 k ,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k 间房屋。

返回小偷的 最小 窃取能力。

示例1:

1
2
3
4
5
6
7
8
输入:nums = [2,3,5,9], k = 2
输出:5
解释:
小偷窃取至少 2 间房屋,共有 3 种方式:
- 窃取下标 0 2 处的房屋,窃取能力为 max(nums[0], nums[2]) = 5
- 窃取下标 0 3 处的房屋,窃取能力为 max(nums[0], nums[3]) = 9
- 窃取下标 1 3 处的房屋,窃取能力为 max(nums[1], nums[3]) = 9
因此,返回 min(5, 9, 9) = 5

提示1:

1
2
3
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^9
1 <= k <= (nums.length + 1)/2

二分 + DP

看到「最大化最小值」或者「最小化最大值」就要想到二分答案,这是一个固定的套路。

注意到窃取房屋数和窃取能力满足单调性,所以可以二分窃取能力,取满足条件的最小值(右边界)即可,也就是满足至少偷窃 $k$ 间房屋。

具体来说,定义 $get(max)$ 返回窃取能力不超过 $max$ 时最多能偷窃的房屋数,如果某个 $max$ 大于等于题目中的 $k$ ,那么显然 $max$ 的右边都满足题意,所以可以继续缩小右边界,反之类似。

剩下问题就是 $get(max)$ 的实现了,可以通过动态规划解决,定义 $dp[i]$ 表示前 $i$ 个房子窃取能力不超过 $max$ 时的最大房屋数,那么有:

  • 第 i 个房子不偷,此时 $dp[i] = dp[i - 1]$
  • 第 i 个房子偷,需满足 $nums[i] <= max$ ,此时 $dp[i] = dp[i - 2] + 1$

取两者最大值即可
$$
\begin{align}
&dp[i] = max(dp[i - 1], dp[i - 2] + 1)         
\end{align}
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int minCapability(int[] nums, int k) {
int n = nums.length;
int left = 0, right = 1000000000;
while(left < right){
int mid = left + (right - left >> 1);
if(get(mid, nums) < k){
left = mid + 1;
}else{
right = mid;
}
}
return left;
}

private int get(int max, int[] nums){
int n = nums.length;
int[] dp = new int[n + 2];
for(int i = 2; i <= n + 1; i++){
dp[i] = dp[i - 1];
if(nums[i - 2] <= max){
dp[i] = Math.max(dp[i], dp[i - 2] + 1);
}
}
return dp[n + 1];
}
}

可以滚动变量

1
2
3
4
5
6
7
8
9
10
11
...
private int getMaxHouse(int maxSteal, int[] nums){
int pre2 = 0, pre1 = 0;
for(int i = 0; i < nums.length; i++){
int cur = nums[i] <= maxSteal ? Math.max(pre1, pre2 + 1) : pre1;
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
...

二分 + 贪心

另一种解法是二分 + 贪心,二分的过程同上。

而对于求窃取能力不超过 $mid$ 时最多能偷窃的房屋数,可以顺序的遍历房屋,如果某个房屋小于 $mid$ 时,贪心的偷取该房子即可。

直觉上就是正确的,也可以证明:因为 $dp[i - 1] = max(dp[i - 2] , dp[ i - 3] + 1)$ ,且显然有 $dp[i] >= dp[i - 1]$ ,也就是说 $dp[i - 2] >= dp[i - 3]$ ,可以得出 $dp[i - 1] <= max(dp[i - 2], dp[i - 2] + 1)$ ,即 $dp[i - 1] <= dp[i - 2] + 1$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {

public int minCapability(int[] nums, int k) {
int left = 0, right = 1000000000;
while(left < right){
int mid = left + (right - left >> 1), curK = 0;
for(int i = 0; i < nums.length; i += nums[i] > mid ? 1 : 2){
curK += nums[i] <= mid ? 1 : 0;
}
if(curK < k){
left = mid + 1;
}else{
right = mid;
}
}
return left;
}

}

LC-6346.打家劫舍IV
https://wecgwm.github.io/2023/02/06/LC-6346-打家劫舍IV/
作者
yichen
发布于
2023年2月6日
许可协议