二分查找原理
二分查找是应用于 顺序 序列的一种算法,在查找的过程中我们在 保证目标值在查找范围 的前提下不断 缩小 查找范围,最终找到目标值
首先介绍最朴素的二分查找算法,这个算法用于定位给定的目标值,如果找不到则返回 -1
首先要设定头尾两个指针,然后通过中间值 (head + tail) >> 1
进行比较,如果中间值和目标值相等则直接返回目标值,否则进行如下操作
{ a r r [ m i d ] < x , h e a d = m i d + 1 a r r [ m i d ] > x , t a i l = m i d − 1 \begin{cases} arr[mid]<x,head=mid+1 \\ arr[mid] > x,tail = mid - 1 \end{cases} { arr[mid]<x,head=mid+1arr[mid]>x,tail=mid−1
这里的操作很容易想明白,目标是为了保证目标在查找范围内缩小查找范围
function binarySearch (nums: number[], target: number) {
let h = 0, t = nums.length - 1, mid: number
while (h <= t) {
mid = (t + h) >> 1
if (nums[mid] === target) return mid
if (nums[mid] < target) h = mid + 1
else t = mid - 1
}
return -1
}
01模型
00001111
这个模型通常用来找 最后一个0
和 第一个1
这里的 最后一个0
可以理解为 最后一个小于等于x 的值;
而 第一个1
可以理解为 第一个大于等于x 的值。
这里以 查找第一个大于等于x的值为例 进行推理
// 01-1 核心部分
while (h < t) {
// 中间指针
mid = (h + t) >> 1
// 范围缩小选择
if (nums[mid] <= target) h = mid
else t = mid - 1
}
// 01-1 核心代码
function b_binarySearch (nums: number[], target: number) {
let h = 0, t = nums.length - 1, mid: number
while (h < t) {
mid = (h + t) >> 1
// mid = h + ((t - h) >> 1)
if (nums[mid] >= target) t = mid
else h = mid + 1
}
return h
}
同理得出 01-0
代码
// 01-0 核心代码
function s_binarySearch (nums: number[], target: number) {
let h = 0, t = nums.length - 1, mid: number
while (h < t) {
mid = h + ((t - h) >> 1) + 1 // 无论什么情况都往右偏一位
// parseInt(`${(h + t + 1) / 2}`) // 奇数时找到中间值,偶数时找到中间偏右
if (nums[mid] <= target) h = mid
else t = mid - 1
}
return h
}
中间指针的偏向
在上面的代码中可以发现 01-0
和 01-1
的 mid
有细微的差别
在搜索序列中如果元素个数是奇数的话,二者定位到的中间值是一样的;但如果是偶数, 01-0
定位到的是中间偏右的值,而 01-1
定位到的是中间偏左的值
如果是右偏的话,有两种情况
- 如上所述,奇数时定位到正中间,偶数时定位到中间偏右的数字
mid = parseInt(`${ (h + t + 1) / 2}`)
- 指针均向右偏一位
mid = ((h + t) >> 1) + 1 mid = h + ((t - h) >> 1) + 1 // 在静态语言中,这种写法可以防止溢出
这两种情况都是行得通的
二分查找题目
leetCode 69 x的平方根
这道题可以转化为 查找最后一个小于等于x的值
function mySqrt(x: number): number {
let h = 0, t = x, mid: number
while (h < t) {
mid = ((h