#蓝桥#JAVA#打家劫舍4
题目描述
沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。
由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。
小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。
给你一个整数数组 nums
表示每间房屋存放的现金金额。形式上,从左起第 i
间房屋中放有 nums[i]
美元。
另给你一个整数 k
,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k
间房屋。
返回小偷的 最小 窃取能力。
示例 1:
输入: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. 确定二分查找的左右边界
- 左边界
lower
:通过Arrays.stream(nums).min().getAsInt()
找出数组nums
中的最小值。这是因为最小的能力值至少要能够偷取到数组中金额最小的房屋。 - 右边界
upper
:通过Arrays.stream(nums).max().getAsInt()
找出数组nums
中的最大值。这是因为最大的能力值最多为数组中金额最大的房屋的金额。
2. 二分查找过程
- 使用
while(lower <= upper)
循环进行二分查找,只要左边界小于等于右边界,就继续查找。 - 在每次循环中,计算当前左右边界的中间值
middle
,作为本次猜测的能力值。
3. 检查当前能力值是否满足条件
- 初始化计数器
count
为 0,用于记录在当前能力值下可以偷取的不相邻房屋的数量。 - 初始化布尔变量
visited
为false
,用于标记上一个房屋是否被偷取。 - 遍历数组
nums
中的每个元素x
:- 如果
x <= middle
且!visited
,说明当前房屋的金额小于等于当前猜测的能力值,并且上一个房屋没有被偷取,此时可以偷取当前房屋,将count
加 1,并将visited
标记为true
。 - 否则,将
visited
标记为false
,表示当前房屋不被偷取。
- 如果
4. 根据检查结果调整二分查找的边界
- 如果
count >= k
,说明在当前能力值下可以偷取到至少k
个不相邻的房屋,当前能力值可能过大,将右边界upper
更新为middle - 1
,缩小查找范围。 - 否则,说明当前能力值过小,将左边界
lower
更新为middle + 1
,扩大查找范围。
5. 返回结果
- 当二分查找结束后,左边界
lower
即为满足条件的最小能力值,将其返回。
class Solution {
public int minCapability(int[] nums, int k) {
int lower = Arrays.stream(nums).min().getAsInt();
// 使用 Java 8 的 Stream API 对数组 nums 进行操作
// Arrays.stream(nums) 将数组转换为流
// min() 方法找出流中的最小值
// getAsInt() 方法将 OptionalInt 类型的结果转换为 int 类型
// 最终将数组中的最小值赋给变量 lower,作为二分查找的左边界
int upper = Arrays.stream(nums).max().getAsInt();
// 同样使用 Stream API
// max() 方法找出流中的最大值
// getAsInt() 方法将结果转换为 int 类型
// 把数组中的最大值赋给变量 upper,作为二分查找的右边界
while(lower <= upper){
// 开始二分查找的循环,只要左边界小于等于右边界,就继续循环
int middle = (lower + upper)/2;
// 计算当前左右边界的中间值,作为本次猜测的能力值
int count = 0;
// 初始化一个计数器 count,用于记录在当前能力值下可以偷取的房屋数量
boolean visited = false;
// 初始化一个布尔变量 visited,用于标记上一个房屋是否被偷取
for(int x :nums){
// 遍历数组 nums 中的每个元素 x
if(x <= middle&&!visited){
// 如果当前房屋的金额 x 小于等于当前猜测的能力值 middle,并且上一个房屋没有被偷取
count ++;
// 则偷取当前房屋,计数器 count 加 1
visited = true;
// 标记当前房屋已被偷取
}else{
visited = false;
// 否则,标记当前房屋未被偷取
}
}
if(count >= k){
// 如果在当前能力值下可以偷取的房屋数量 count 大于等于要求的数量 k
upper = middle -1;
// 说明当前能力值可能过大,将右边界 upper 更新为 middle - 1,缩小查找范围
}else{
lower = middle+1;
// 否则,说明当前能力值过小,将左边界 lower 更新为 middle + 1,扩大查找范围
}
}
return lower;
// 当二分查找结束后,左边界 lower 即为满足条件的最小能力值,将其返回
}
}
同时,利用二分法这里给出了更优的解决方案
class Solution {
public int minCapability(int[] nums, int k) {
int n = nums.length;
int max = 0, min = Integer.MAX_VALUE;
// 初始化 max 为 0,用于存储数组中的最大值
// 初始化 min 为 Integer.MAX_VALUE,用于存储数组中的最小值
for(int x:nums){
// 遍历数组 nums 中的每个元素 x
if(x < min) min = x;
// 如果当前元素 x 小于 min,则更新 min 为 x
if(x > max) max = x;
// 如果当前元素 x 大于 max,则更新 max 为 x
}
int left = min, right = max;
// 初始化二分查找的左边界 left 为数组的最小值
// 初始化二分查找的右边界 right 为数组的最大值
while(left <= right){
// 开始二分查找的循环,只要左边界小于等于右边界,就继续查找
int mid = left + right >> 1;
// 计算当前左右边界的中间值 mid,作为本次猜测的能力值
// 这里使用位运算 >> 1 代替除以 2,以提高计算效率
int count = 0;
// 初始化计数器 count,用于记录在当前能力值下可以偷取的房屋数量
for(int i = 0; i < n; ++i){
// 遍历数组 nums
if(nums[i] <= mid){
// 如果当前房屋的金额 nums[i] 小于等于当前猜测的能力值 mid
if(++count == k)
break;
// 先将计数器 count 加 1,若加 1 后 count 等于 k,说明已经找到了 k 个满足条件的房屋,退出循环
++i;
// 跳过下一个房屋,确保偷取的房屋不相邻
}
}
if(count >= k)
right = mid - 1;
// 如果在当前能力值下可以偷取的房屋数量 count 大于等于 k,说明当前能力值可能过大,将右边界 right 更新为 mid - 1,缩小查找范围
else
left = mid + 1;
// 否则,说明当前能力值过小,将左边界 left 更新为 mid + 1,扩大查找范围
}
return left;
// 当二分查找结束后,左边界 left 即为满足条件的最小能力值,将其返回
}
}