二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
查找过程:
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
算法要求:
1.必须采用顺序存储结构。
2.必须按关键字大小有序排列。
比较次数:
计算公式:
当顺序表有n个关键字时:
查找失败时,至少比较a次关键字;
查找成功时,最多比较关键字次数是b。
注意:a,b,n均为正整数。
算法复杂度:
二分查找的基本思想是将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果xa[n/2],则只要在数组a的右半部搜索x.
时间复杂度即是while循环的次数。
总共有n个元素,
渐渐跟下去就是n,n/2,n/4,…n/2^k(接下来操作元素的剩余个数),其中k就是循环的次数
由于你n/2^k取整后>=1
即令n/2^k=1
可得k=log2n,(是以2为底,n的对数)
所以时间复杂度可以表示O(h)=O(log2n)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
class Solution {
public int search(int[] nums, int target) {
}
}
class Solution {
public int search(int[] nums, int target) {
// 初始化 left 与 right 指针
int left = 0;
int right = nums.length - 1;
// 由于两个指针相交时,当前元素还未判断,因此需要取等号
while (left <= right) {
// 先找到区间的中间元素
int mid = left + (right - left) / 2;
// 分三种情况讨论
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 如果程序⾛到这⾥,说明没有找到⽬标值,返回 -1
return -1;
}
}
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
class Solution {
public int[] searchRange(int[] nums, int target) {
}
}
⽤的还是⼆分思想,就是根据数据的性质,在某种判断条件下将区间⼀分为⼆,然后舍去其中⼀个区间,然后再另⼀个区间内查找;
⽅便叙述,⽤ x 表⽰该元素, resLeft 表⽰左边界, resRight 表⽰右边界。
寻找左边界:
左边区间 [left, resLeft - 1] 都是⼩于 x 的;
右边区间(包括左边界) [resLeft, right] 都是⼤于等于 x 的;
因此,关于 mid 的落点,我们可以分为下⾯两种情况:
当我们的 mid 落在 [left, resLeft - 1] 区间的时候,也就是 arr[mid]
当 mid 落在 [resLeft, right] 的区间的时候,也就是 arr[mid] >= target 。说明 [mid + 1, right] (因为 mid 可能是最终结果,不能舍去)是可以舍去的,此时更新 right 到 mid 的位置,继续在 [left, mid] 上寻找左边界
由此,就可以通过⼆分,来快速寻找左边界;
注意:这⾥找中间元素需要向下取整。
因为后续移动左右指针的时候:
左指针: left = mid + 1 ,是会向后移动的,因此区间是会缩⼩的;
右指针: right = mid ,可能会原地踏步(⽐如:如果向上取整的话,如果剩下 1,2 两个元素, left == 1 ,right == 2 , mid == 2 。更新区间之后, left,right,mid 的值没有改变,就会陷⼊死循环)。因此⼀定要注意,当 right = mid 的时候,要向下取整
寻右左边界:
因此,关于 mid 的落点,我们可以分为下⾯两种情况:
由此,就可以通过⼆分,来快速寻找右边界;
注意:这⾥找中间元素需要向上取整。
因为后续移动左右指针的时候:
左指针: left = mid ,可能会原地踏步(⽐如:如果向下取整的话,如果剩下 1,2 两个元素, left == 1, right == 2,mid == 1 。更新区间之后, left,right,mid 的值没有改变,就会陷⼊死循环)。
右指针: right = mid - 1 ,是会向前移动的,因此区间是会缩⼩的;
因此⼀定要注意,当 right = mid 的时候,要向下取整
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] ret = new int[2];
ret[0] = ret[1] = -1;
// 处理边界情况
if(nums.length == 0) {
return ret;
}
// 1. ⼆分左端点
int left = 0;
int right = nums.length - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
// 判断是否有结果
if(nums[left] != target) {
return ret;
} else {
ret[0] = right;
}
// 2. ⼆分右端点
left = 0;
right = nums.length - 1;
while(left < right) {
int mid = left + (right - left + 1) / 2;
if(nums[mid] <= target) {
left = mid;
}else {
right = mid - 1;
}
}
ret[1] = left;
return ret;
}
}
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
class Solution {
public int searchInsert(int[] nums, int target) {
}
}
1、分析插⼊位置左右两侧区间上元素的特点:
设插⼊位置的坐标为 index ,根据插⼊位置的特点可以知道:
[left, index - 1] 内的所有元素均是⼩于 target 的;
[index, right] 内的所有元素均是⼤于等于 target 的。
2、设 left 为本轮查询的左边界, right 为本轮查询的右边界。根据 mid 位置元素的信
息,分析下⼀轮查询的区间:
3、 直到我们的查找区间的⻓度变为 1 ,也就是left == right 的时候, left 或者
right 所在的位置就是我们要找的结果
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else {
right = mid;
}
}
// 特判⼀下第三种情况
if (nums[right] < target) {
return right + 1;
}
return right;
}
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
class Solution {
public int mySqrt(int x) {
}
}
设 x 的平⽅根的最终结果为 index :
分析 index 左右两次数据的特点:
[0, index] 之间的元素,平⽅之后都是⼩于等于 x 的;
[index + 1, x] 之间的元素,平⽅之后都是⼤于 x 的。
因此可以使⽤⼆分查找算法。
class Solution {
public int mySqrt(int x) {
long left = 1;
long right = x;
if(x < 1) {
return 0;
}
while(left < right) {
long mid = left + (right - left + 1)/2;
if(mid*mid <= x) {
left = mid;
} else {
right = mid - 1;
}
}
return (int)left;
}
}
关于《【算法优选】 二分查找专题——壹》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!