[Leetcode] 二分查找算法指南(进阶篇)

本文深入探讨了二分查找算法在LeetCode中解决复杂问题的应用,包括猜数字大小、在D天内送达包裹的能力、有序矩阵中第K小的元素等多个实例,详细解析了解题思路和如何巧妙地运用二分法来解决问题。
摘要由CSDN通过智能技术生成
前言

在上一篇 二分查找算法指南(基础篇)中,博主带大家总结了二分查找的基本模板,并且一起讨论了几道典型的例题。在今天的文章中,我们将着重讨论二分算法的一些难点题。

问题形式

这类问题往往第一眼看不出使用二分查找,因为它们大都不是单纯对元素下标进行查找就能判断查找方向,也没有固定的问法,但还是有共同点可循。它们往往需要对某个区间的值进行计算,得出结果后再与条件比较从而解题。

具体问题

让我们先从简单的问题开始热身吧。

猜数字大小

我们正在玩一个猜数字游戏。 游戏规则如下:
我从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。
你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
-1 : 我的数字比较小
1 : 我的数字比较大
0 : 恭喜!你猜对了!

示例
输入:n = 10, pick = 6
输出:6
解题思路

这道题其实是每个人都玩过的猜数字的游戏,根据经验我们知道每次折半猜就能在最少的次数下猜到正确结果。由于题目中已经给了我们定义好的接口,我们直接调用就能知道当前是猜小了还是猜大了,因此根据接口返回值来判断二分查找的方向。

public int guessNumber(int n) {
   
    int left = 1, right = n;
    while (left <= right) {
   
        int mid = (left + right) >>> 1;
        int t = guess(mid);
        if (t == 0) return mid;
        else if (t == 1) left = mid + 1; // 我们猜小了,往更大的范围猜
        else right = mid - 1; // 我们猜大了
    }   
    return left;
}
在 D 天内送达包裹的能力

传送带上的包裹必须在 D D D 天内从一个港口运送到另一个港口。
传送带上的第 i i i 个包裹的重量为 w e i g h t s [ i ] weights[i] weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D D D 天内将传送带上的所有包裹送达的船的最低运载能力。

示例
输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10
请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。 
解题思路

这道题乍一看似乎和二分法没什么关系,而且题干给人的感觉更像是要用动态规划来做。我们不妨换个角度看问题,为了能成功地运送所有的包裹,那么我们的运载能力最低必须大于最重的那个包裹,那么假如有 5 个同样重量的包裹,我们需要花 5 天来运,对吧?然后假如我们有一艘无限装货的船,那么我们 1 天就可以运完所有的货物,此时船的运载能力只需要等于所有货物的总重量。
为了恰好在 D D D 天内运完所有的包裹,我们需要一艘合适的船。假如我们的船选太大,那么不到 D D D 天就能运完,太浪费。而假如我们的船选小了,那就超过了规定的 D D D 天期限。是不是有点猜数字的感觉了?所以这里隐藏的二分法思想就是,对于船的运载能力,我们需要计算对应的天数,来和 D D D 天作比较,那么我们就能折半选择合适的船了。

public int shipWithinDays(int[] weights, int D) {
   
	int low = 0, high = 0; // low 表示最小的船,high 表示最大的船
	for (int w : weights) {
   
		low = Math.max(low, w);
		high += w;
	}
	while (low < high) {
   
		int capacity = (low + high) >>> 1;
		if (requireDays(weights, D, capacity) > D) {
   
			low = capacity + 1; // 需要的天数大于 D, 说明船太小了
		} else {
   
			high = capacity; // 否则,说明船太大了
		}
	}
	return low;
}

private int requireDays(int[] weights, int D, int capacity) {
   
	int days = 1, curWeight = 0;
	for (int w : weights) {
   
		if (curWeight + w > capacity) {
    // 装不下更多包裹了
			days += 1; // 今天先送走一波
			curWeight = 0; // 送完回来了,船又空了
		}
		curWeight += w; // 继续装货
	}
	return days;
}

怎么样?这么一分析是不是顿时思维开拓?其实本质上和猜数字一样,只不过需要我们自己实现子函数来判断接下来的折半查找方向。和这道题非常类似的还有 爱吃香蕉的珂珂最小化去加油站的最大距离。此外,博主在整理时还发现这道题的代码和 分割数组的最大值 完全一样,直接拿来就能用,然而那题还是困难,这题却是中等。足以说明 Leetcode 的难度是越来越高了╮(╯▽╰)╭。

有序矩阵中第K小的元素

给定一个 n ∗ n n * n nn 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k k k 小的元素。
请注意,它是排序后的第 k k k 小元素,而不是第 k k k 个元素。

示例
matrix = [
  [ 1,  5,  9],
  [10, 11, 13],
  [12, 13, 15]
],
k = 8,
返回 13。
解题思路

本题似乎和上一篇中讨论的 搜索二维矩阵 类似,但细看可以发现每行最后一个元素不一定大于下一行第一个元素,所以我们不能用那题的方法。不过我们依然可以用二分法来做。
首先,由于每行每列是有序的,那么左上角的数字一定是最小的,而右下角的数字一定是最大的,这就是搜索区间。我们可以利用这个性质,从数组的左下角开始查找,如果比目标值小,我们就向右移一位,而且我们知道当前列的当前位置的上面所有的数字都小于目标值,那么加上 i + 1 i + 1 i+1 个数,反之则向上移一位,这样我们也能算出 c o u n t count count 的值。

public int kthSmallest(int[][] matrix, int k) {
   
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值