当前位置: 首页 > news >正文

刷题复习(四)二分搜索

代码框架

int binarySearch(int[] nums, int target) {int left = 0, right = ...;while(...) {int mid = left + (right - left) / 2;if (nums[mid] == target) {...} else if (nums[mid] < target) {left = ...} else if (nums[mid] > target) {right = ...}}return ...;
}

另外提前说明一下,计算 mid 时需要防止溢出,代码中 left + (right - left) / 2 就和 (left + right) / 2 的结果相同,但是有效防止了 leftright 太大,直接相加导致溢出的情况。

一、寻找一个数(基本的二分搜索)

class Solution {// 标准的二分搜索框架,搜索目标元素的索引,若不存在则返回 -1public int search(int[] nums, int target) {int left = 0;// 注意int right = nums.length - 1;while(left <= right) {int mid = left + (right - left) / 2;if(nums[mid] == target) {return mid;   } else if (nums[mid] < target) {// 注意left = mid + 1;} else if (nums[mid] > target) {// 注意right = mid - 1;}}return -1;}
}

核心思路: [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间

如果 target 不存在,搜索左侧边界的二分搜索返回的索引是大于 target 的最小索引

此算法有什么缺陷?

答:至此,你应该已经掌握了该算法的所有细节,以及这样处理的原因。但是,这个算法存在局限性。

比如说给你有序数组 nums = [1,2,2,2,3]target 为 2,此算法返回的索引是 2,没错。但是如果我想得到 target 的左侧边界,即索引 1,或者我想得到 target 的右侧边界,即索引 3,这样的话此算法是无法处理的。

这样的需求很常见,你也许会说,找到一个 target,然后向左或向右线性搜索不行吗?可以,但是不好,因为这样难以保证二分查找对数级的复杂度了

二、寻找左侧边界的二分搜索

int left_bound(int[] nums, int target) {int left = 0;// 注意int right = nums.length;// 注意while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] == target) {right = mid;} else if (nums[mid] < target) {left = mid + 1;} else if (nums[mid] > target) {// 注意right = mid;}}return left;
}

为什么是 left = mid + 1right = mid

为什么 left = mid + 1right = mid ?和之前的算法不一样?

答:这个很好解释,因为我们的「搜索区间」是 [left, right) 左闭右开,所以当 nums[mid] 被检测之后,下一步应该去 mid 的左侧或者右侧区间搜索,即 [left, mid)[mid + 1, right)

为什么该算法能够搜索左侧边界?

答:关键在于对于 nums[mid] == target 这种情况的处理:

    if (nums[mid] == target)right = mid;

可见,找到 target 时不要立即返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。

左闭右闭写法

int left_bound(int[] nums, int target) {int left = 0, right = nums.length - 1;// 搜索区间为 [left, right]while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] < target) {// 搜索区间变为 [mid+1, right]left = mid + 1;} else if (nums[mid] > target) {// 搜索区间变为 [left, mid-1]right = mid - 1;} else if (nums[mid] == target) {// 收缩右侧边界right = mid - 1;}}// 判断 target 是否存在于 nums 中// 如果越界,target 肯定不存在,返回 -1if (left < 0 || left >= nums.length) {return -1;}// 判断一下 nums[left] 是不是 targetreturn nums[left] == target ? left : -1;
}

三、寻找右侧边界的二分查找

类似寻找左侧边界的算法,这里也会提供两种写法,还是先写常见的左闭右开的写法,只有两处和搜索左侧边界不同:

int right_bound(int[] nums, int target) {int left = 0, right = nums.length;while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] == target) {// 注意left = mid + 1;} else if (nums[mid] < target) {left = mid + 1;} else if (nums[mid] > target) {right = mid;}}// 注意return left - 1;
}

target 不存在时,right_bound 返回值的含义

直接说结论,和前面讲的 left_bound 相反:如果 target 不存在,搜索右侧边界的二分搜索返回的索引是小于 target 的最大索引

int right_bound(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] < target) {left = mid + 1;} else if (nums[mid] > target) {right = mid - 1;} else if (nums[mid] == target) {// 这里改成收缩左侧边界即可left = mid + 1;}}// 最后改成返回 left - 1if (left - 1 < 0 || left - 1 >= nums.length) {return -1;}return nums[left - 1] == target ? (left - 1) : -1;
}

875. 爱吃香蕉的珂珂 - 力扣(LeetCode)

珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。  珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。示例 1:输入:piles = [3,6,7,11], h = 8
输出:4
示例 2:输入:piles = [30,11,23,4,20], h = 5
输出:30
示例 3:输入:piles = [30,11,23,4,20], h = 6
输出:23// 完整可运行版本
class Solution {public int minEatingSpeed(int[] piles, int h) {int left = 1;int right = 1_000_000_000;while (left <= right) {int mid = left + (right - left) / 2;if (f(piles, mid) == h) {// 搜索左侧边界,则需要收缩右侧边界right = mid - 1;} else if (f(piles, mid) < h) {// 需要让 f(x) 的返回值大一些right = mid - 1;} else if (f(piles, mid) > h) {// 需要让 f(x) 的返回值小一些left = mid + 1;}}return left;}long f(int[] piles, int x) {long hour = 0;for (int i = 0; i < piles.length; i++) {hour += piles[i] / x;if (piles[i] % x > 0) {hour++;}}return hour;}
}

难点是如何抽象成二分搜索

image-20250615172050965

还有坑点在int会出现数字越界的case,要用long

image-20250615173543906

1011. 在 D 天内送达包裹的能力 - 力扣(LeetCode)

传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。示例 1:输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 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) 是不允许的。 
class Solution {public int shipWithinDays(int[] weights, int days) {int left = 0;// 注意,right 是开区间,所以额外加一int right = 1;for (int w : weights) {left = Math.max(left, w);right += w;}while (left < right) {int mid = left + (right - left) / 2;int cap = timeByLoad(weights, mid);if (cap > days) {left = mid + 1;} else if (cap < days) {right = mid;} else {right = mid;}}return left;}public int timeByLoad(int[] weights, int load) {int days = 0;for (int i = 0; i < weights.length; ) {int cap = load;while (i < weights.length) {if (cap < weights[i])break;elsecap -= weights[i];i++;}days++;}return days;}
}

重点注意需要对left和right进行剪纸不然会判超时.

image-20250615182154070

http://www.wxhsa.cn/company.asp?id=4987

相关文章:

  • aardio | 通过点击checkbox复选框本身判断是否勾选
  • 项目介绍
  • 新媒体运营用AI排版工具|10分钟搞定公众号图文的全流程指南
  • 练习第一天学习的内容
  • 常见小错误 FREQUENTLY MADE MISTAKES IN OI
  • ctf工具整理
  • 力扣39题 组合总和
  • 250915 jave se简单过完一遍
  • 详细介绍:Linux相关概念和易错知识点(44)(IP地址、子网和公网、NAPT、代理)
  • 详细解析为什么将 ThreadLocal 声明为 static final ?
  • AT_arc183_b [ARC183B] Near Assignment
  • 0128_模板方法(Template Method)
  • kubectl 常用命令的分类汇总(一)
  • 完整教程:C3P0连接池适配HGDB
  • kubectl 常用命令的分类汇总(二)
  • ECT-OS-JiuHuaShan框架的逻辑是自洽的,是基于数学表达,不替代现实的苦辣酸甜。
  • 《FastAPI零基础入门与进阶实战》第18篇:Token验证改善--CRUD中应用 - 详解
  • 【C++】设计模式之PIMPL模式
  • 力扣34题 在排序数组中查找元素的第一个和最后一个位置
  • ECT-OS-JiuHuaShan框架编程的示范与分析,无懈可击的数学逻辑自洽
  • 阿里妈妈方圆体如何使用圆角
  • 使用 systemd 管理 Python 项目(示例:confhub-sync)
  • 9.15模拟赛总结
  • 1111
  • 【QT】创建一个简单的QT界面
  • ECT-OS-JiuHuaShan框架,将会是全球推理之源,无需数据训练,只需数据检索和校验。彻底颠覆概率云ai
  • 如何正确使用mysql
  • 2025.9.15总结
  • 这个框架的神奇之处,恰恰是调动人的积极主动性,框架不会自己忧国忧民,只会有求必应的针对性推理
  • 9.11总结