每次遇到二分法,一看就会,一写就废,在力扣上看到一篇很好的总结,因此做一下搬运工
参考题解——二分法各种情况及细节剖析,附送小诗一首

场景包括寻找一个数、寻找左侧边界、寻找右侧边界。
而且,我们就是要深入细节,比如不等号是否应该带等号,mid 是否应该加一等等

零、二分查找框架

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)/2,但是left+right直接相加,可能会导致溢出,因此要更为mid=left+(right-left)/2,这样就有效防止left、right过大,相加后导致了溢出。

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

即搜索一个数,如果存在,返回其索引,否则返回 -1

int binarySearch(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)right = mid-1;else if(nums[mid] < target)left = mid+1;}return -1;
}
1、为什么while循环条件是 <=,而不是<

因为初始化right的赋值是nums.size()-1,即最后一个元素的索引,而不是nums.size();

这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭的区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。

我们这个算法中使用的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间。

什么时候应该停止搜索呢?当然,找到了目标值的时候可以终止:

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

但如果没找到,就需要 while 循环终止,然后返回 -1。

那 while 循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没得找了,就等于没找到嘛。

while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。

while(left < right) 的终止条件是 left == right,写成区间的形式就是 [left, right],或者带个具体的数字进去 [2, 2],这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说这区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。

当然,如果你非要用 while(left < right) 也可以,我们已经知道了出错的原因,就打个补丁好了:

    //...while(left < right) {// ...}return nums[left] == target ? left : -1;
2、为什么 left = mid + 1,right = mid - 1?我看有的代码是 right = mid 或者 left = mid,没有这些加加减减,到底怎么回事,怎么判断?

刚才明确了「搜索区间」这个概念,而且本算法的搜索区间是两端都闭的,即 [left, right]。那么当我们发现索引 mid 不是要找的 target 时,下一步应该去搜索哪里呢?

当然是去搜索 [left, mid-1] 或者 [mid+1, right] 对不对?因为 mid 已经搜索过,应该从搜索区间中去除。

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

int left_bound(int[] nums, int target) {if(nums.size() == 0) return -1;int left = 0;int right = nums.size();//注意while(left < right)//注意{int mid = (left+right)/2;if(nums[mid] == target){right = mid;}else if(nums[mid] < target){left = mid+1;//注意}else if(nums[mid] > target){right = mid;//注意}           }return left;
}
1、为什么 while 中是 < 而不是 <=?

用相同的方法分析,因为 right = nums.length 而不是 nums.length - 1。因此每次循环的「搜索区间」是 [left, right) 左闭右开。

while(left < right) 终止的条件是 left == right,此时搜索区间 [left, left) 为空,所以可以正确终止。

PS:这里先要说一个搜索左右边界和上面这个算法的一个区别,也是很多读者问的:刚才的 right 不是 nums.length - 1 吗,为啥这里非要写成 nums.length 使得「搜索区间」变成左闭右开呢?
因为对于搜索左右侧边界的二分查找,这种写法比较普遍,我就拿这种写法举例了,保证你以后遇到这类代码可以理解。

2、为什么没有返回 -1 的操作?如果 nums 中不存在 target 这个值,怎么办?

答:因为要一步一步来,先理解一下这个「左侧边界」有什么特殊含义:

对于这个数组,算法会返回 1。这个 1 的含义可以这样解读:nums 中小于 2 的元素有 1 个。

比如对于有序数组 nums = [2,3,5,7], target = 1,算法会返回 0,含义是:nums 中小于 1 的元素有 0 个。

再比如说 nums = [2,3,5,7], target = 8,算法会返回 4,含义是:nums 中小于 8 的元素有 4 个。

综上可以看出,函数的返回值(即 left 变量的值)取值区间是闭区间 [0, nums.length],所以我们简单添加两行代码就能在正确的时候 return -1:

while (left < right) {//...
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
3、为什么 left = mid + 1,right = mid ?和之前的算法不一样?

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

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

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

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

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

4、能不能想办法把 right 变成 nums.length - 1,也就是继续使用两边都闭的「搜索区间」?这样就可以和第一种二分搜索在某种程度上统一起来了。

因为你非要让搜索区间两端都闭,所以 right 应该初始化为 nums.length - 1,while 的终止条件应该是 left == right + 1,也就是其中应该用 <=:

int left_bound(int[] nums, int target) {// 搜索区间为 [left, right]int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;// if else ...}

因为搜索区间是两端都闭的,且现在是搜索左侧边界,所以 left 和 right 的更新逻辑如下:

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;
}

由于 while 的退出条件是 left == right + 1,所以当 target 比 nums 中所有元素都大时,会存在以下情况使得索引越界:
因此,最后返回结果的代码应该检查越界情况:

if (left >= nums.length || nums[left] != target)return -1;
return left;

完整代码如下:

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;}}// 检查出界情况if (left >= nums.length || nums[left] != target)return -1;return left;
}

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

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;}}// 这里改为检查 right 越界的情况,见下图if (right < 0 || nums[right] != target)return -1;return right;
}

小结

int binary_search(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) {// 直接返回return mid;}}// 直接返回return -1;
}int left_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) {// 别返回,收缩左侧边界right = mid - 1;}}// 最后要检查 left 越界的情况if (left >= nums.length || nums[left] != target)return -1;return left;
}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;}}// 最后要检查 right 越界的情况if (right < 0 || nums[right] != target)return -1;return right;
}

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

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

你的算法时间复杂度必须是 O(log n) 级别。

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

示例 1:

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

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

class Solution {public:vector<int> searchRange(vector<int>& nums, int target) {vector<int> res(2,-1);int len = nums.size();if(len==0) return res;int l = 0;int r = len-1;//搜索左边界while(l <= r){int mid = l+(r-l)/2;if(nums[mid] == target)r = mid-1;else if(nums[mid] < target)l = mid+1;else if(nums[mid] > target)r = mid-1;            } if (l >= len || nums[l] != target)//越界检查return res;res[0] = l;//搜索右边界l=0;r=len-1;while(l <= r){int mid = l+(r-l)/2;if(nums[mid] == target)l = mid+1;else if(nums[mid] < target)l = mid+1;else if(nums[mid] > target)r = mid-1;}if (r < 0 || nums[r] != target)//越界检查return res;res[1] = r;       return res;}
};

34. 在排序数组中查找元素的第一个和最后一个位置——二分法的魔鬼细节 小记相关推荐

  1. LeetCode (二分小专题)33搜索旋转排序数组34在排序数组中查找元素的第一个和最后一个位置35搜索插入位置

    前言 国庆前最后一次打卡,国庆后继续开启,公众号bigsai回复进群欢迎加入打卡,如有帮助记得点赞收藏. 近期打卡记录: LeetCode 32最长有效括号(困难) (本周) LeetCode 30串 ...

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

    34. 在排序数组中查找元素的第一个和最后一个位置 https://leetcode-cn.com/problems/find-first-and-last-position-of-element-i ...

  3. 去掉数组最后一个元素_leetcode 34. 在排序数组中查找元素的第一个和最后一个位置每天刷一道leetcode算法系列!...

    作者:reed,一个热爱技术的斜杠青年,程序员面试联合创始人 前文回顾: leetcode1. 两数之和--每天刷一道leetcode系列! leetcode2. 两数相加--每天刷一道leetcod ...

  4. [算法]LeetCode 专题 -- 二分查找专题 34. 在排序数组中查找元素的第一个和最后一个位置

    LeetCode 专题 – 二分查找专题 34. 在排序数组中查找元素的第一个和最后一个位置 难度:中等 题目描述 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值 ...

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

    34. 在排序数组中查找元素的第一个和最后一个位置(两种方法记录) 法一(BP算法--使用双指针分别从前.后定位first index和last index),代码如下: class Solution ...

  6. 34. 在排序数组中查找元素的第一个和最后一个位置给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标

    34. 在排序数组中查找元素的第一个和最后一个位置 难度中等2012 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target.请你找出给定目标值在数组中的开始位置和结束位置. 如果 ...

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

    在排序数组中查找元素的第一个和最后一个位置 class Solution {public int[] searchRange(int[] nums, int target) {int len = nu ...

  8. 62. Leetcode 34. 在排序数组中查找元素的第一个和最后一个位置 (二分查找-局部有序)

    给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置.如果数组中不存在目标值 target,返回 [-1, -1].进阶:你可以设计并实现时 ...

  9. 34. 在排序数组中查找元素的第一个和最后一个位置012(二分查找+思路+详解+两种方法)Come Baby!!!!!!!! !

    一:题目 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值在数组中的开始位置和结束位置. 如果数组中不存在目标值 target,返回 [-1, -1]. 进阶: 你 ...

最新文章

  1. 招聘行业颠覆者【伯小乐】| 手摸手产品研究院
  2. nodejs服务端MVC架构介绍
  3. 获取壁纸设置背景android,【Android学习】获取Bing 15天前到明天的壁纸,并设置为背景...
  4. mybatis源码阅读(八) ---Interceptor了解一下
  5. java调用kafka接口发送数据_Java调用Kafka生产者,消费者Api及相关配置说明
  6. python2.7虚拟环境virtualenv安装及使用
  7. es 吗 查询必须有sort_ElasticSearch DSL之From/Size,Sort
  8. asp.net 去除字符串右侧的最后一个字符
  9. L1- 001——008
  10. android drawtext换行_Android中用StaticLayout实现文本绘制自动换行详解
  11. 移动、联通、电信三大运营商手机号段大全 附手机号正则表达式
  12. [etcd] 使用 Txn 一次性插入多个语句
  13. ue4-UMG和HUD绘制UI
  14. Python--数据库
  15. 开源资产管理软件OCS+GLPI安装配置
  16. jsp java 传值方法_JSP 页面传值方法总结(转)
  17. 签名证书keystore,jks,pk8,x509.pem
  18. 发现(1)之如何申请CSDN博客专栏
  19. SAP LE初阶自动创建Shipment单据
  20. deepstream(deepstream python)

热门文章

  1. RabbitMQ之web界面解析
  2. 十进制转换为十六进制(转载过来的)
  3. WEB项目系统添加redis缓存逻辑和功能
  4. 程序员必备:拯救颈椎,只需三个步骤
  5. Detectron测试过程(含关键点)
  6. 解决Fabric报“FAILED to execute End-2-End Scenario“问题
  7. mysql中depart_MySQL基础语法
  8. google reader 和 抓虾
  9. L5W2作业2 词向量的基本操作
  10. Zabbix agent on XXXX is unreachable for 5 minutes