二分搜索应用 II
1. 题目列表
题目列表:
序号 | 题目 | 难度 |
---|---|---|
1 | 410. 分割数组的最大值 | 困难 |
2 | 658. 找到 K 个最接近的元素 | 中等 |
3 | 2594. 修车的最少时间 | 中等 |
4 | 2560. 打家劫舍 IV | 中等 |
5 | 162. 寻找峰值 | 中等 |
6 | 1552. 两球之间的磁力 | 中等 |
2. 应用
2.1. Leetcode 410. 分割数组的最大值
2.1.1. 题目
给定一个非负整数数组
和一个整数 ,你需要将这个数组分成 个非空的连续子数组。设计一个算法使得这 个子数组各自和的最大值最小。
示例 1:
输入:
,
输出:
解释:
一共有四种方法将 nums 分割为 2 个子数组。其中最好的方式是将其分为和 。因为此时这两个子数组各自的和的最大值为 ,在所有情况中最小。
提示:
2.1.2. 解题思路
题目中要求将数组
设数组
-
二分查找的右侧区间就是
; -
二分查找的右侧区间就是
。
那么,只需要在闭区间
2.1.3. 代码实现
class Solution { public int splitArray(int[] nums, int k) { int sum = 0; int maxNum = 0; for (int num : nums) { sum += num; maxNum = Math.max(maxNum, num); } int left = maxNum; int right = sum; while (left < right) { int mid = left + (right - left) / 2; // 分割次数大于k,说明每一段的目标和太小了,需要缩小左区间 if (check(nums, k, mid)) { left = mid + 1; } else { right = mid; } } return right; } private boolean check(int[] nums, int k, int target) { int splitCount = 1; // 以当前的区间中点作为子数组的和,可以分割的次数 int sum = 0; for (int num : nums) { if (sum + num <= target) { sum += num; } else { // 若分割次数大于k,则需要缩小区间左边界 splitCount++; if (splitCount > k) { return true; } sum = num; } } return false; } }
2.2. Leetcode 658. 找到 K 个最接近的元素
2.2.1. 题目
给定一个 排序好 的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
或者 且
示例 1:
输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]
2.2.2. 解题思路
假设数组
跟查找一个具体的数值不同的是,这里我们需要查找一个符合条件的子区间,并且在查找过程中,因为我们希望找到与
这样,每次比较的时候,需要比较下一个区间的起点
2.2.3. 复杂度
-
时间复杂度:
-
空间复杂度:
2.2.4. 代码实现
class Solution { public List<Integer> findClosestElements(int[] arr, int k, int x) { int left = 0, right = arr.length - k - 1; while (left <= right) { int mid = left + (right - left) / 2; if (x - arr[mid] <= arr[mid + k] - x) { right = mid - 1; } else { left = mid + 1; } } List<Integer> result = new ArrayList<>(); for (int i = left; i < left + k; i++) { result.add(arr[i]); } return result; } }
2.3. Leetcode 2594. 修车的最少时间
2.3.1. 题目
给你一个整数数组 ranks ,表示一些机械工的 能力值 。
是第 i 位机械工的能力值。能力值为 r 的机械工可以在 分钟内修好 n 辆车。
同时给你一个整数 cars ,表示总共需要修理的汽车数目。
请你返回修理所有汽车 最少 需要多少时间。
注意:所有机械工可以同时修理汽车。
示例 1:
输入:ranks = [4,2,3,1], cars = 10
输出:16
解释:
- 第一位机械工修 2 辆车,需要 4 * 2 * 2 = 16 分钟。
- 第二位机械工修 2 辆车,需要 2 * 2 * 2 = 8 分钟。
- 第三位机械工修 2 辆车,需要 3 * 2 * 2 = 12 分钟。
- 第四位机械工修 4 辆车,需要 1 * 4 * 4 = 16 分钟。
16 分钟是修理完所有车需要的最少时间。
2.3.2. 解题思路
对于能力值为
对于修车的时间
-
如果
分钟内,所有的汽车都可以修理完,那么,大于等于 分钟时,也可以将所有汽车修理完; -
如果
分钟内,所有的汽车都不能修理完,那么,小于等于 分钟时,也不能将所有汽车修理完。
那么,我们可以枚举时刻来查找满足条件的时间。
二分查找的边界:
-
下界:由于车辆最少有
辆,所以,修改好一辆车至少需要一分钟,因此,下边界为 。 -
上界:任何一个人独立完成工作的所需的时间,必然大于所有人一起完成工作所需的时间,因此,可以取任意一个人独立完成所需的时间为上边界。
所以,二分查找的区间范围:
其中,
因此,我们可以在闭区间内使用二分查找,找到满足条件的左边界即可。
2.3.3. 复杂度
-
时间复杂度:
其中,
是数组 的长度。 -
空间复杂度:
2.3.4. 代码实现
class Solution { public long repairCars(int[] ranks, int cars) { // 任何一个工人修完所有车辆的时间一定大于等于最优解 long left = 1, right = (long) ranks[0] * cars * cars; while (left < right) { long mid = left + (right - left) / 2; if (check(ranks, cars, mid)) { right = mid; } else { left = mid + 1; } } return left; } private boolean check(int[] ranks, int cars, long minute) { long count = 0; for (int rank : ranks) { count += (long) Math.sqrt(1.0 * minute / rank); } return count >= cars; } }
2.4. Leetcode 2560. 打家劫舍 IV
2.4.1. 题目
沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。
由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。
小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。
给你一个整数数组 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 。
2.4.2. 解题思路
通过分析,容易看出,小偷的偷窃能力
对于每一个能力值
-
大于能力值的房间都不能偷窃;
-
小于能力值的房间,且其相邻房间未被偷窃,那么,它可以被偷窃。
因此,我们可以考虑使用二分查找,满足条件的最小能力值。
注意,在查找过程中,我们需要用到贪心的思想,即对于每一个能力值,越早偷窃,留给剩余房子偷窃的机会就越多。
如果房间数量为
2.4.3. 复杂度
-
时间复杂度:
-
空间复杂度:
2.4.4. 代码实现
class Solution { public int minCapability(int[] nums, int k) { int left = Integer.MAX_VALUE, right = Integer.MIN_VALUE; for (int num : nums) { right = Math.max(right, num); left = Math.min(left, num); } while (left <= right) { int mid = left + (right - left) / 2; if (check(nums, k, mid)) { right = mid - 1; } else { left = mid + 1; } } return left; } private boolean check(int[] nums, int k, int capability) { int count = 0; boolean visited = false; for (int num : nums) { if (num <= capability && !visited) { count++; visited = true; } else { visited = false; } } return count >= k; } }
2.5. Leetcode 162. 寻找峰值
2.5.1. 题目
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设。
你必须实现时间复杂度为的算法来解决此问题。
示例 1:
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:
- 你的函数可以返回索引 1,其峰值元素为 2;
- 或者返回索引 5, 其峰值元素为 6。
2.5.2. 解题思路
从题意可知,峰值元素一定严格大于左右相邻值的元素。
题目中可能有多个峰值元素,那么,对于任意两个相邻的峰谷之间的局部元素它是满足单调性的,并且数组中没有相邻的重复元素,因此,我们可以通过二分查找,确定峰值的位置。
每次二分时区间的中点,必然落在某两个相邻的峰谷之间,即
-
如果元素出现在峰值的左侧,移动左指针;
-
如果元素出现在峰值的右侧,移动右指针;
这样,就可以通过二分查找,在
这里,我们可以将题目等价于一个在闭区间内查找左边界的二分查找,第一个不满足条件的元素,就是题目的答案之一。
类似地,我们也可以将其看成查找右边界的二分查找。
2.5.3. 复杂度
-
时间复杂度:
-
空间复杂度:
2.5.4. 代码实现
- 查找左边界的思路
class Solution { public int findPeakElement(int[] nums) { int n = nums.length; if (n == 1) { return 0; } int left = 0, right = n - 1; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] < nums[mid + 1]) { left = mid + 1; } else { right = mid; } } return left; } }
-
查找右边界的思路
参考:162. 寻找峰值
2.6. Leetcode 1552. 两球之间的磁力
2.6.1. 题目
2.6.2. 解题思路
假设数组
那么,题目可以等价于:从有序数组
求所有组合中,最小的
这是一个求解极小值极大化的题目,显然,为了使最小差值最大,我们每次需要挑选的两个数字
假设所求的目标值为
-
当
时,能选择的数字个数必然小于 ; -
当
时,能选择的数字个数必然大于等于 。
那么,我们使用二分查找求解满足条件的
2.6.3. 复杂度
-
时间复杂度:
;其中,排序需要的时间复杂度为
,二分查找的复杂度为 -
空间复杂度:
。排序需要的栈空间,复杂度为
。
2.6.4. 代码实现
class Solution { public int maxDistance(int[] position, int m) { Arrays.sort(position); int n = position.length; int result = -1; int left = 0, right = position[n - 1] - position[0]; while (left <= right) { int mid = left + (right - left) / 2; if (check(position, mid, m)) { left = mid + 1; result = mid; } else { right = mid - 1; } } return result; } private boolean check(int[] position, int distance, int m) { int last = position[0], count = 1; for (int i = 1; i < position.length; i++) { if (position[i] - last >= distance) { last = position[i]; count += 1; } } return count >= m; } }
3. 总结
二分查找不需要关注区间内的元素具有什么性质,而是需要关注区间外面的元素具有什么性质。
参考:
本文作者:LARRY1024
本文链接:https://www.cnblogs.com/larry1024/p/17978345
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步