二分查找
二分查找是一种在每次比较之后将查找空间一分为二的算法。每次需要查找集合中的索引或元素时,都应该考虑二分查找。如果集合是无序的,我们可以总是在应用二分查找之前先对其进行排序。
时间复杂度是: log N。因为,二分查找是通过将现有数组一分为二来执行的。每次调用子例程(或完成一次迭代)时,其大小都会减少到现有部分的一半。迭代的最大次数是 log N (base 2) 。
最常用的模板
二分查找一般由三个主要部分组成:
- 预处理 —— 如果集合未排序,则进行排序
- 二分查找 —— 使用循环或递归在每次比较后将查找空间划分为两半。
- 后处理 —— 在剩余空间中确定可行的候选者。
先介绍学校中学习的常用模板(由于每次都判断nums[mid]==target 无需后处理):
public int binarySearch(int[] nums, int target) {
int right=nums.length-1;
int left=0;
while(left<=right)
{
int mid=(left+right)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]>target) right=mid-1;
else left=mid+1;
}
return -1;
}
初始条件:left = 0, right = length-1
终止:left <= right
向左查找:right = mid-1
向右查找:left = mid+1
高阶模板
终止条件修改为:left+1>right始终保留区间内大于两个元素,因此需要对剩下的两个元素进行后处理
int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left + 1 < right){
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid;
} else {
right = mid;
}
}
//后处理
if(nums[left] == target) return left;
if(nums[right] == target) return right;
return -1;
}
算法应用
模板一
猜数字:从 1 到 n 选择一个数字。 你需要猜我选择了哪个数字。
每次你猜错了,我会告诉你这个数字是大了还是小了。你调用一个预先定义好的接口 guess(int num),它会返回 3 个可能的结果(-1,1 或 0):
public class Solution extends GuessGame {
public int guessNumber(int n) {
long left=0,right=n;
while(left<=right)
{
long mid=(left+right)/2;
if(guess((int)mid)==0)
return (int)mid;
else if(guess((int)mid)==-1)
right=mid-1;
else
left=mid+1;
}
return -1;
}
}
模板二
寻找峰值:峰值元素是指其值大于左右相邻值的元素。
要求:给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。你可以假设 nums[-1] = nums[n] = -∞。
class Solution {
public int findPeakElement(int[] nums) {
if(nums==null) return -1;
if(nums.length==1) return 0;
int left=0,right=nums.length;
while(left+1<right)
{
int mid= left+(right-left)/2;
if((mid-1==-1||nums[mid]>nums[mid-1])&& //mid-1可能越界,先做判断
(mid+1==nums.length||nums[mid]>nums[mid+1]))
return mid;
else if (nums[mid]<nums[mid-1])
right=mid;
else
left=mid;
}
if(left-1==-1||nums[left]>nums[left-1]&&nums[left]>nums[left+1])
return left;
if(nums[right]>nums[right-1]&&
right+1==nums.length||nums[right]>nums[right+1])
return right;
return -1;
}
}
核心:分治思想
二分查找的核心思想就是分治思想
pow(n,x)
分治思想还可以解决其他的一些问题:pow(n,x)的实现,即计算 x 的 n 次幂函数。
class Solution {
public double myPow(double x, int n) {
return n>0?quickMul(x,n):1/quickMul(x,-n);
}
public double quickMul(double x, int n)
{
if(n==0) return 1;
if(n==1) return x;
double y=quickMul(x,n/2);
return n%2==0?y*y:y*y*x;
}
}
寻找比目标字母大的最小字母
给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。
在比较时,字母是依序循环出现的。举个例子:
如果目标字母 target = ‘z’ 并且字符列表为 letters = [‘a’, ‘b’],则答案返回 ‘a’.
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
int left=0,right=letters.length-1,mid=0;
while(left<=right)
{
mid=left+(right-left)/2;
if(letters[mid]==target)
{
while(mid+1<letters.length&&letters[mid]==letters[mid+1])
mid++;
break;
}
else if(letters[mid]<=target)
left=mid+1;
else
right=mid-1;
}
if(target<letters[mid])
return letters[mid];
return letters[(mid+1)%letters.length];
}
}