赞
踩
前言:
作为查找板块中最重要的算法和思想,二分查找是典型的一看就会,一做就废。要不要加=?要不要+1?要不要-1?这是二分查找最让人头痛的地方,作为一个思想不难,细节制胜的算法,拒绝死记硬背,本文将详细解析它的算法思路和原理。
目录
二、寻找左侧边界(最大值最小化or在单调序列中找x或其前驱)
三、寻找右侧边界(最小值最大化or在单调序列中找x或其后继)
顾名思义,该方法是在一段区间内寻找target目标值,并且左边界和右边界都可以取到。
左边界和右边界都可以取到,看似非常简单,但是是本方法控制边界和细节的关键所在。
- int binarySearch(vector<int> &nums, int target) {
- //初始化区间
- int left = 0, right = nums.size() - 1;
- while (left <= right)
- {
- //小细节:为什么不直接用(left+right)/2
- //这就涉及到程序执行先后顺序的问题了
- //直接先i+j的话有可能发生整数溢出的问题
- //而下面这种方法便可巧妙地一定程度上规避这种问题
- int mid = left + (right - left) / 2;
- if (nums[mid] < target)
- left = mid + 1;
- else if (nums[mid] > target)
- right = mid - 1;
- else
- return mid;
- }
- // 未找到目标元素,返回 -1
- return -1;
- }
1.当target>nums[mid]时,说明target在[mid+1,right]上,因此left=mid+1;
2.当target<nums[mid]时,说明target在[left,mid-1]上,因此right=mid-1;
3.当target=nums[mid]时,说明已找到,返回target;
同样,还是因为它是双闭区间,left=right时也可以存在区间[left,right]
看到这里可能还是理解的不太深刻,看接下来这个对比例子就明白了
- int binarySearch(vector<int> &nums, int target) {
- //初始化区间
- int left = 0, right = nums.size();
- while (left < right)
- {
- //小细节:为什么不直接用(left+right)/2
- //这就涉及到程序执行先后顺序的问题了
- //直接先i+j的话有可能发生整数溢出的问题
- //而下面这种方法便可巧妙地一定程度上规避这种问题
- int mid = left + (right - left) / 2;
- if (nums[mid] < target)
- left = mid + 1;
- else if (nums[mid] > target)
- right = mid ;
- else
- return mid;
- }
- // 未找到目标元素,返回 -1
- return -1;
- }
根据上面的对比分析,我们可以发现,它的细节其实原理非常简单,一切细节的设计都是围绕区间的开闭展开,不过要特别说明的是,我们通常更习惯的是采用左闭右闭的双闭区间形式,这样左右都是对称操作的,更不容易出错。
本文下面提到的算法均采用双闭区间的形式。
- int binarySearchInsertion(vector<int> &nums, int target) {
- int l = 0, r = nums.size() - 1;
- while (l <= r) {
- int m = l + (r - l) / 2;
- if (nums[m] < target) {
- l = m + 1;
- } else if (nums[m] > target) {
- r = m - 1;
- } else {
- l = m - 1;
- }
- }
- return l;
- }
不难发现,二分查找无非就是给指针i和j分别设定搜索目标,在不断的循环二分中,让l和r都逐渐逼近预先设定的目标,总的来说就是一个不断向目标奔赴的过程。
- int binarySearchInsertion(vector<int> &nums, int target) {
- int l = 0, r = nums.size() - 1;
- while (l <= r) {
- int m = l + (r - l) / 2;
- if (nums[m] < target) {
- l = m + 1;
- } else if (nums[m] > target) {
- r = m - 1;
- } else {
- l = m +1;
- }
- }
- return r;
- }
通过上面三种算法的介绍,我们已经初步掌握了二分的思想,并且这三种方法可以用来解决绝大部分二分的题目,不过要注意巧妙灵活的变形和运用
下面介绍二分的一种经典应用思想:
当我们容易得出结果具有单调性时,可以先从小到大假设结果,然后设置一个check函数看该答案是否能使题目成立,主要用到的二分思想是寻找左侧边界和右侧边界,因为题目通常是问最小值或最大值,下面是该思想的几道经典题目:
本文详细介绍了二分的细节实现,这是一种很重要的思想,一定要多加练习,想不清楚时多画图进行实践验证。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。