最朴素二分查找算法:
最朴素二分查找算法:
最基础的一道题:基于有序数组的二分查找--. - 力扣(LeetCode)
基本流程:
1:实际上二分查找不一定非得要有序,能否使用二分查找的关键是是否具有二段性.
2:基于有序数组的二分查找算法也没有规定必须要均分.对于有序数组不论你取哪个点都具有二段性.只不过在中间取分割点的方式效率最高,时间复杂度为Olog(n);
时间复杂度:
从有序数组的二分查找我们可以抽象出来朴素二分查找的模板:
省略的内容是确定二段性的过程,需要自己根据题目找出确定二段性的方式
不朴素二分查找
不朴素二分查找其实就是二分查找的循环里左右指针的行为为:1 :left = mid + 1,right = mid或者2: left = mid right = mid - 1这两种时的二分查找。这里给出模板解决上述俩种情况的边界处理问题。
以. - 力扣(LeetCode)"在排序数组中查找元素的第一个和最后一个位置"为例:
直接通过朴素二分查找确定的元素无法确定他在目标区间的位置,也就不知道是不是第一个和最后一个位置的元素.所以朴素二分查找无法解决这道题.
这道题我们可以通过题目分解为俩部分:1:找第一个位置 2:找最后一个位置
找目标元素出现的第一个位置即目标元素左端点:
第一段(数组左端到left)为不包含目标元素的区间,第二段(right到数组右端)为包含目标元素的区间。mid指针不断移动对俩个区间进行扩张直到找出左端点。具体来说:
*如果mid小于target,left = mid + 1;
*如果mid大于等于target,right = mid;
*循环结束条件:while(left < right)
这里的二段性怎么分析出来的? 注意这里找的是左端点.左端点就能得出来一个特性,左端点的左边一定是不包含目标元素.由此我们将数组分为:包含目标元素和不包含目标元素俩段.俩段的临界点即我们要去拿到的左端点
细节:
1:循环结束的判断:一定要为left < right,如果是left <= right 那么在判定到最终结果时,判定条件为if(nums[mid]>=nums[right]) right=mid,此时就会陷入到死循环当中。
2:对于mid的确定(元素个数为偶数时mid的位置为偏左一个数还是偏右一个数):mid一定要得到偏左那个数,即left + (right - left)/2. 原因:如果到了left和right指针相邻的情况时,mid落在了右边那个数,那么right = mid,mid再次计算位置不变这个过程就形成了一个死循环。(剩下俩个数左边那个一定小于或者等于嘛,右边那个数一定大于或者等于嘛,结合left和right的移动规则就知道要到偏左那个位置)
模板:
找目标元素出现的最后一个位置即目标元素右端点:
对于右端点来说,它的二段性进行了改变:
第一段:包含target的区间;(0,left]
第二段:不包含target的区间。(right , nums.length - 1)
所以left和right的行为相比较与左端点的求法就需要进行改变:
*mid <= right left = mid
*mid > target right = mid - 1.
这里left和right的移动规则进行了改变,则mid的确定方法就得进行改变。mid需要确定到偏右的位置,否则就会进入死循环,所以要使用:left + (right - left + 1)/2
其它注意事项和原理与求左端点基本相同.
实例:
模板:
在遇到二分查找的问题时,我们首先根据题意确定左右指针的行为,行为确定后如果是 不朴素二分查找我们就直接通过上述模板确定循环结束条件,mid的表达式等。
例题:
不朴素二分查找:
非递减数组求目标元素的左右端点 :. - 力扣(LeetCode)
旋转数组最小值. - 力扣(LeetCode)将数组抽象成图形就会发现其二段性(俩个升序段,且第二个升序的最大值比第一段小):以第一个元素或者最后一元素为flag,mid值比flag大说明mid在第一段,否则在第二段。所以左右指针的行为就应该是:if(nums[mid] < flag)right = mid; else left = mid + 1然后套用模板解决问题
x的平方根:. - 力扣(LeetCode)举个例子:8的平方根在2和3之间,但是这里的规则是取2即小数直接去掉。所以做指针的行为就应该是if(nums[left]的平方 <= taget)left = mid else right = mid - 1.然后套用模板就能解决问题。
搜索插入位置:. - 力扣(LeetCode)按照题意不存在目标值target时返回第一个大于目标值的下标。所以左右指针的行为就是:if(nums[left] < target)left = mid + 1 else right = mid
然后套用模板即可
山脉数组峰顶索引:. - 力扣(LeetCode)
利用山脉的上升和下降通过mid和mid + 1值比较判断二段性。上升阶段left可以压缩到mid + 1下降阶段righ可以压缩到 mid所以left和right行为:
if(arr[mid] >= arr[mid + 1]) right = mid;else left = mid + 1;然后套用模板。
点名:. - 力扣(LeetCode)二段性:缺席之前下标等于学号,缺席之后下标不等于学号。下标和对应的学号进行比较得到二段性。当 下标 != 学号 时right压缩到mid,当下标 == 学号时left 压缩到mid + 1.由此得到left和right指针行为:if(mid != records[mid]) right = mid;else left = mid + 1;然后套用模板即可