1. 二分查找法介绍
1.1 二分查找法概念
先来一段维基百科概念。“二分查找算法,也称折半搜索算法,是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。”
简单来说,就是在序列中找到一个分割点,使得我们需要找的答案一定在某一边的子序列而不在另一边的子序列,之后继续在找到子序列中给出分割点,无限二分下去直到找到目标,这使得原本需要一次遍历的查找时间复杂度降为了 O ( lg N ) O(\lg N) O(lgN).
这里有很重要的一点是:使用二分查找不一定要求数组是有序的。只需要能够找到一个分割点,将序列分为两个类别即可,通常来说这个分割点用中点。上述最基础的二分法是分为哪两个类别呢?小于中间值的为一类,大于中间值的为另一类。如果在无序的数组中,可以将数组按不同的方法分类。
这里,可以给出一个无序数组使用二分查找的例子:
Leetcode[162] Find Peak Element
【题干】峰值元素是指其值大于左右相邻值的元素。给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。你可以假设 nums[-1] = nums[n] = -∞ 。
【题解】峰值的必然存在性使得我们是可以用二分法来进行处理。先找出序列最中间的值nums[mid],如果当前值相比于其右边的值更大,说明左半边必然有一个峰值;否则右半边必然有一个峰值。由于本题要求是找到一个峰值即可,因此使用二分法是可行的。
本题是如何分类的:一边必然至少有一个峰值;一边不确定有没有峰值。因此我们二分可以放弃另一边来减少搜索次数。
【代码】
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while(l < r){
int mid = l + r >> 1; //中间点
if(nums[mid] > nums[mid + 1]) r = mid;
else l = mid + 1;
}
return l;
}
因此,记住一点,使用二分法不一定要求有序,只要求可以确定答案一定会出现在其中一边即可。
1.2 二分法类型
使用二分法的题有两种类型,一种是序列的二分查找,一种是数值的二分查找。
序列的二分查找类似上述的常规二分,一半都是给定一个序列,找到符合某个要求的值。二维序列的二分查找事实上和一维序列类似,这里不将其分为两类。
数值的二分查找即给定一个数字和一些条件,并且可以知道答案必然在这个数字(或整数最大值)和某个值(通常为0或1)之间。
之后分别给出这两种类型问题的二分模版。
1.3 二分法模版
- 序列二分模版(1)
public int binarySearch(int[] nums){
int l = 0, r = nums.length - 1;
while(l < r){
int mid = l + ((r - l) >> 1); //整数不溢出可以写成 : l + r >> 1
if(check(mid, xxx) ==</