news 2026/3/10 18:19:16

Leetcode 86 【二分查找】在排序数组中查找元素的第一个和最后一个位置

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Leetcode 86 【二分查找】在排序数组中查找元素的第一个和最后一个位置

二分搜索算法核心代码模板 | labuladong 的算法笔记

1 温故而知新

题目

704. 二分查找

给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果target存在返回下标,否则返回-1

你必须编写一个具有O(log n)时间复杂度的算法。
示例 1:

输入:nums= [-1,0,3,5,9,12],target= 9输出:4解释:9 出现在nums中并且下标为 4

示例 2:

输入:nums= [-1,0,3,5,9,12],target= 2输出:-1解释:2 不存在nums中因此返回 -1

提示:

  1. 你可以假设nums中的所有元素是不重复的。
  2. n将在[1, 10000]之间。
  3. nums的每个元素都将在[-9999, 9999]之间。

代码实现

重新写的时候犯了个小错误,贴上解答错误的代码

class Solution { public: int search(vector<int>& nums, int target) { int left = 0 ; int right = nums.size() -1 ; while (left < right){ int mid = (right - left) / 2 + left ; if (nums[mid] == target){ return mid; }else if (nums[mid] > target){ right = mid - 1 ; }else{ left = mid + 1 ; } } return -1; } };

错误:循环条件,left <= right ,两个可以相等,对于数组内只有单个元素的考虑(否则会错误的跳出循环返回 -1)

class Solution { public: int search(vector<int>& nums, int target) { int left = 0 ; int right = nums.size() - 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; } };

框架伪码

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

题目

34. 在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组nums,和一个目标值target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值target,返回[-1, -1]

你必须设计并实现时间复杂度为O(log n)的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6输出:[-1,-1]

示例 3:

输入:nums = [], target = 0输出:[-1,-1]

代码实现

这个题目怎么觉得有点像滑动窗口,直接看看以前提交的代码回顾一下。

using namespace std ; class Solution { public: int left_bound(vector<int>& nums ,int target){ int left = 0 , right = nums.size() - 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){ right = mid - 1 ; } } if (left < 0 || left >= nums.size() || nums[left] != target){ return -1 ; } return left ; } int right_bound(vector<int>& nums ,int target){ int left = 0 , right = nums.size() - 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 ; } } if (right < 0 || right >= nums.size() || nums[right] != target){ return -1 ; } return right ; } vector<int> searchRange(vector<int>& nums, int target) { int left = left_bound(nums,target); int right = right_bound(nums,target); return{ left , right }; } };

我知道了,一看这个题解,我知道我当时根本不会这个题目,我先记下来的。

class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { // 直接调用原名称的左右边界查找函数 int left = left_bound(nums, target); int right = right_bound(nums, target); return {left, right}; } private: // 查找左边界 int left_bound(vector<int>& nums, int target) { int left = 0, right = nums.size()-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) { right = mid - 1; // 锁定左侧边界 } } // 越界或值不匹配则返回-1 if (left < 0 || left >= nums.size() || nums[left] != target) { return -1; } return left; } // 查找右边界 int right_bound(vector<int>& nums, int target) { int left = 0, right = nums.size()-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; // 锁定右侧边界 } } // 越界或值不匹配则返回-1 if (right < 0 || right >= nums.size() || nums[right] != target) { return -1; } return right; } };

其实很清晰了,就是在做一个左边界查找和右边界查找索引的工作,根本没那么难。

自己动手写的错的代码

class Solution { private: int left_bound (vector<int>& nums, int target){ int left = 0 ; int right = nums.size() -1 ; while(right >= left){ int mid = left + (right - left) / 2 ; if(nums[mid] == target){ right = mid - 1; }else if (nums[mid] > target){ right = mid - 1; }else{ left = mid +1 ; } } if (left < 0 || left >= nums.size() || nums[left] != target) { return -1; } return left; } int right_bound (vector<int>& nums, int target){ int left = 0 ; int right = nums.size() -1; while(right >= left){ int mid = left + (right - left) / 2 ; if(nums[mid] == target){ left = mid +1 ; }else if (nums[mid] > target){ right = mid - 1; }else{ left = mid +1 ; } } if (left < 0 || left >= nums.size() || nums[left] != target) { return -1; } return right; } public: vector<int> searchRange(vector<int>& nums, int target) { int left = left_bound(nums,target); int right = right_bound(nums,target); return{ left, right }; } };

错误注释

class Solution { private: int left_bound (vector<int>& nums, int target){ int left = 0 ; int right = nums.size() -1 ; while(right >= left){ int mid = left + (right - left) / 2 ; if(nums[mid] == target){ right = mid - 1; }else if (nums[mid] > target){ right = mid - 1; }else{ left = mid +1 ; } } // 错误1(非致命):left初始为0且只增不减,不可能<0,这个判断多余 if (left < 0 || left >= nums.size() || nums[left] != target) { return -1; } return left; // left_bound的返回值是正确的,这里没问题 } int right_bound (vector<int>& nums, int target){ int left = 0 ; int right = nums.size() -1; while(right >= left){ int mid = left + (right - left) / 2 ; if(nums[mid] == target){ left = mid +1 ; }else if (nums[mid] > target){ right = mid - 1; }else{ left = mid +1 ; } } // 错误2(致命):校验的是left,但right_bound的结果存在right里,left此时可能越界 // 比如nums=[1,2,2,3], target=2,循环结束后left=3,nums[3]=3≠2,直接错误返回-1 // 正确应该校验right的合法性和值 if (left < 0 || left >= nums.size() || nums[left] != target) { return -1; } // 这里return right是正确的,但上面的校验逻辑错了,导致根本走不到这一步 return right; } public: vector<int> searchRange(vector<int>& nums, int target) { int left = left_bound(nums,target); int right = right_bound(nums,target); return{ left, right }; } };

修正后

class Solution { private: int left_bound (vector<int>& nums, int target){ int left = 0 ; int right = nums.size() -1 ; while(right >= left){ int mid = left + (right - left) / 2 ; if(nums[mid] == target){ right = mid - 1; }else if (nums[mid] > target){ right = mid - 1; }else{ left = mid +1 ; } } if (left >= nums.size() || nums[left] != target) { return -1; } return left; } int right_bound (vector<int>& nums, int target){ int left = 0 ; int right = nums.size() -1; while(right >= left){ int mid = left + (right - left) / 2 ; if(nums[mid] == target){ left = mid +1 ; }else if (nums[mid] > target){ right = mid - 1; }else{ left = mid +1 ; } } if (right>= nums.size() || nums[right] != target) { return -1; } return right; } public: vector<int> searchRange(vector<int>& nums, int target) { int left = left_bound(nums,target); int right = right_bound(nums,target); return{ left, right }; } };

承认偷懒了,校验逻辑这里直接复制粘贴了,没有自己再好好核对一遍。

2 二分查找归类

第一个,最基本的二分查找算法

因为我们初始化 right = nums.length - 1 所以决定了我们的「搜索区间」是 [left, right] 所以决定了 while (left <= right) 同时也决定了 left = mid+1 和 right = mid-1 因为我们只需找到一个 target 的索引即可 所以当 nums[mid] == target 时可以立即返回

第二个,寻找左侧边界的二分查找

因为我们初始化 right = nums.length 所以决定了我们的「搜索区间」是 [left, right) 所以决定了 while (left < right) 同时也决定了 left = mid + 1 和 right = mid 因为我们需找到 target 的最左侧索引 所以当 nums[mid] == target 时不要立即返回 而要收紧右侧边界以锁定左侧边界

第三个,寻找右侧边界的二分查找

因为我们初始化 right = nums.length 所以决定了我们的「搜索区间」是 [left, right) 所以决定了 while (left < right) 同时也决定了 left = mid + 1 和 right = mid 因为我们需找到 target 的最右侧索引 所以当 nums[mid] == target 时不要立即返回 而要收紧左侧边界以锁定右侧边界 又因为收紧左侧边界时必须 left = mid + 1 所以最后无论返回 left 还是 right,必须减一

三类代码合起来看

int binary_search(vector<int>& nums, int target) { int left = 0, right = nums.size()-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) { // 直接返回 return mid; } } // 直接返回 return -1; } int left_bound(vector<int>& nums, int target) { int left = 0, right = nums.size()-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) { // 别返回,锁定左侧边界 right = mid - 1; } } // 判断 target 是否存在于 nums 中 if (left < 0 || left >= nums.size()) { return -1; } // 判断一下 nums[left] 是不是 target return nums[left] == target ? left : -1; } int right_bound(vector<int>& nums, int target) { int left = 0, right = nums.size()-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; } } // 由于 while 的结束条件是 right == left - 1,且现在在求右边界 // 所以用 right 替代 left - 1 更好记 if (right < 0 || right >= nums.size()) { return -1; } return nums[right] == target ? right : -1; }

3 总结

一开始,看上面的题目还是懵懵懂懂,有很多语法的细节没有自己仔细注意。

现在回头看,无非就是3种情况,对应上面的三种逻辑:

1.查找元素

2.查找左边界

3.查找右边界

------------

虽然自己写还是会有点小问题,但是排查能力和自己修正的能力在不断提高,总比一开始什么也看不懂好的多。

我在进步,和纵向的自己比,不要浮躁,不要焦虑,慢慢来,比较快。

好巧,看了这题的初次提交时间刚好是1个月以前。

1个月从完全没思路到自己能理出框架和思路,至少有深一些的理解,我在进步,不要放弃自己,加油。

加油加油!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/9 11:25:57

PaddlePaddle镜像与边缘计算设备的适配策略

PaddlePaddle镜像与边缘计算设备的适配策略 在智能制造车间的一角&#xff0c;一台搭载瑞芯微RK3588芯片的工控机正实时分析流水线上的产品图像。当检测到异常缺陷时&#xff0c;系统在200毫秒内完成推理并触发停机指令——整个过程没有依赖云端&#xff0c;所有AI能力都运行在…

作者头像 李华
网站建设 2026/3/10 9:40:39

Blender版本管理终极指南:5分钟掌握专业级工作流

Blender版本管理终极指南&#xff1a;5分钟掌握专业级工作流 【免费下载链接】Blender-Launcher Standalone client for managing official builds of Blender 3D 项目地址: https://gitcode.com/gh_mirrors/bl/Blender-Launcher 还在为管理多个Blender版本而头疼吗&…

作者头像 李华
网站建设 2026/3/4 14:13:54

Baiduwp-PHP终极Docker部署指南:三分钟快速搭建百度网盘解析服务

Baiduwp-PHP终极Docker部署指南&#xff1a;三分钟快速搭建百度网盘解析服务 【免费下载链接】baiduwp-php A tool to get the download link of the Baidu netdisk / 一个获取百度网盘分享链接下载地址的工具 项目地址: https://gitcode.com/gh_mirrors/ba/baiduwp-php …

作者头像 李华
网站建设 2026/3/8 2:17:53

I2C协议应答信号实现原理:低电平响应机制深入解析

I2C应答机制揭秘&#xff1a;为什么“拉低才是确认”&#xff1f;你有没有在调试I2C通信时遇到过这样的场景&#xff1f;主机发完一个字节&#xff0c;却迟迟收不到从机的回应——逻辑分析仪上清清楚楚地显示&#xff0c;第9个SCL周期里SDA始终是高电平。于是你开始怀疑&#x…

作者头像 李华
网站建设 2026/3/7 12:35:26

手机弹窗终极解决方案:李跳跳自定义规则完整指南

手机弹窗终极解决方案&#xff1a;李跳跳自定义规则完整指南 【免费下载链接】LiTiaoTiao_Custom_Rules 李跳跳自定义规则 项目地址: https://gitcode.com/gh_mirrors/li/LiTiaoTiao_Custom_Rules 还在为手机应用里层出不穷的弹窗而烦恼吗&#xff1f;李跳跳自定义规则项…

作者头像 李华
网站建设 2026/3/10 16:35:11

数字频率计设计地平面分割策略:通俗解释数字/模拟混合布局

数字频率计设计中的地平面分割&#xff1a;从原理到实战的深度拆解你有没有遇到过这样的情况&#xff1f;一个精心设计的数字频率计&#xff0c;硬件电路看起来毫无破绽&#xff0c;软件逻辑也跑得飞快——但一到测量小信号&#xff0c;读数就开始“跳舞”&#xff0c;重复性差…

作者头像 李华