- 查找算法有很多种,例如顺序查找,二分查找,插值查找,斐波那契查找.....,但是最经典好用常考的还是二分查找
- 今天我们就来刷二分查找
一、二分查找基本思路
- 使用条件:
- 用于查找的内容是有序的
- 查找内容中只有一个符合条件
- 时间复杂度:
O(lgn) - 思路
- 首先找到数组中间的数字和目标值进行比较
- 如果就可以直接返回答案
- 如果不相等
- 如果中间的数字大于目标值,则中间数字向右的所有数字都大于目标值,全部排除
- 如果中间的数字小于目标值,则中间数字向左的所有数字都小于目标值,全部排除
- 我们在遇到二分查找时经常会让
mid= (left + right) /2,然而这种算法在left和right都很大的时候容易导致溢出情况,所以推荐使用mid = left + (right-left) /2 - 因为
left+(right-left)/2的结果可能为为带小数数字,例如,left=0,right=5时,结果为2.5,因此需要用Math.floor:向下取整 - 704. 二分查找 - 力扣(LeetCode)
- 难度:简单
- 这道题就是直接对二分查找进行运用
- 我们先采用的是【left,right】 的区间,此时跳出while循环的条件应为
left<=right
var search = function(nums, target) {
let left = 0;
let right = nums.length-1; //这里为nums.length,指向的是有效结点
while(left<=right) { //left<=right跳出结点
let mid = Math.floor(left + (right - left) / 2);
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) {
left = mid +1;
} else if(nums[mid] > target) {
right = mid - 1 ;
}
}
return -1;
};
- 为了区分另一种做法,我们把另一种也写一下,应该为【left,right)的区间,此时跳出while循环的条件为
left < right,当然我的建议还是在做题过程中常用一种方法即可,防止混乱
var search = function(nums, target) {
let left = 0;
let right = nums.length; //因为right无需取到,所以这里直接赋值nums.length,为无效结点
while(left< right) { //left<right 跳出结点
let mid = Math.floor(left + (right - left) / 2);
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) {
left = mid +1;
} else if(nums[mid] > target) {
right = mid ;
}
}
return -1;
};
二、二分查找的应用
- 35. 搜索插入位置 - 力扣(LeetCode)
- 难度:简单
- 这道题一看,即是有序数组,又需要时间复杂度为
Olgn,那么妥妥地就应该考虑到可以用二分查找 - 这道题跟上面有所区别的应该就是最后的返回值为
right+1 - right+1处理了以下三种情况:
- 目标值在所有元素之前【0,-1】; 因此返回0;
- 目标值在所有元素之后【nums.length-1,nums.length-1】,此时因返回nums.length
- 目标值在数组中,此时区间为【right,left】,返回right+1
var searchInsert = function(nums, target) {
let left = 0;
let right = nums.length-1;
while(left <= right) {
let mid = Math.floor(left + (right-left) /2) ;
if(nums[mid] == target) {
return mid
}else if(nums[mid]<target) {
left = mid+1;
}else if(nums[mid] > target) {
right = mid-1
}
}
return right+1
};
- 34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
- 难度:中等
- 这道题跟上一道题也很像,同样是有序数组和要求
Olgn,又可以上我们的二分查找 - 那这道不一样的是,需要返回的是等于target的下标集合,因为等于target的数在跳出循环时可能在mid的左边也可能在mid的右边(
left<=right时通过break跳出),那么我们就需要分别从mid的左边和右边分别寻找寻找 - 如果跳出循环时
right<left,说明数组中找不到符合target的数,此时我们需要返回【-1,-1】
var searchRange = function(nums, target) {
let left = 0;
let right = nums.length -1;
let mid ;
while(left <= right) {
mid = Math.floor(left + (right - left) /2 );
if(nums[mid] == target) {
break;
}else if(nums[mid] < target) {
left = mid + 1;
}else if(nums[mid] > target) {
right = mid - 1;
}
}
if(left > right) return [-1,-1] ; //查找不到,直接返回【-1,-1】
let i = mid, j = mid;
while(nums[i-1] === nums[mid]) i--; //从mid的左边查找
while(nums[j+1] === nums[mid]) j++; //从mid的右边查找
return [i,j]
};
- 69. x 的平方根 - 力扣(LeetCode)
- 难度:简单
- 这道题没有给有序数组没有明摆着告诉你可以用二分查找,但是数字是递增的,然后也是返回唯一解,相当于暗戳戳的告诉你了用二分是不错的解法(BuShi)
- 因为数字开方最大等于它本身(等于1时),因此right可以赋值为它本身
- 这道题
right>left退出循环,此时应该返回right
var mySqrt = function(x) {
let left = 0;
let right = x;
while(left<=right) {
let mid = Math.floor(left+ (right-left)/2);
if(mid * mid == x ) {
return mid
}else if(mid * mid < x) {
left = mid+1
}else if(mid * mid > x) {
right = mid -1;
}
}
return right
};
- 875. 爱吃香蕉的珂珂 - 力扣(LeetCode)
- 难度:中等
- 上面的题都属于比较简单直接把二分查找的思路套进去即可,这次我们来刷一道看起来综合一点的
- 要用二分查找法,首先要找到left和right分别对应什么,这道题我们的目标是找到速度值,即每个小时吃的香蕉数,那么按照题意理解,我们给出的范围最小值应该为1,即每一个小时只吃一个香蕉,最大值应该为最大堆的香蕉数(也就是以吃得最快的速度,再多也是浪费)
- 其次目标是为了找出最优速度,那么如果能够在h时间内吃完,即缩小速度(
right = mid +1)继续尝试,如果不能吃完(left = mid+1),即增大速度继续尝试,直到left>right
var minEatingSpeed = function(piles, h) {
let left = 1;
let right = 0;
//得出最大堆香蕉数,也就是right值
for(const pile of piles) {
right = Math.max(right, pile);
}
while(left <= right) {
let mid = Math.floor(left + (right-left) /2) ;
let time = getTime(piles,mid);
if(time <= h) {
right = mid-1;
}else if(tiem>h) {
left = mid+1;
}
}
return left
};
var getTime = function(piles, mid) {
let time = 0
for(let item of piles) {
if(item <= mid) time++;
if(item > mid) time += Math.ceil(item/mid);
}
return time
}
我们通过上述五道题学习巩固了一下二分查找,会发现二分查找的套路基本是一样的,只需要找到left,right分别应该是什么和应该返回的是什么值对基本思路进行替换即可, 还可以做以下题进行巩固
367. 有效的完全平方数 - 力扣(LeetCode) 难度:简单
剑指 Offer II 069. 山峰数组的顶部 - 力扣(LeetCode)难度:简单
其他算法题还可以看