携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情
二分搜索算法是一种在有序数组中查找某一特定元素的算法。但是二分搜索并非仅使用在有序数组中(看似与前句矛盾,但这种情况下篇文章详述)。
它的优点是非常高效,时间复杂度为O(logn)
二分搜索的解题思路:
- 确定搜索的范围和区间边界
- 求中间值看是否满足条件,若满足返回
- 若不满足,则根据判断确定在中间值的左边还是右边进行搜索
二分搜索算法通常有两种解法:递归或者循环
下面用几道例题熟悉一下:
33.搜索旋转排序数组
依据题意,可知虽然该数组整体并非有序,但是只要选定中点,那么一定有一侧是有序的,根据这个规律,即可进行二分搜索:
nums[mid]>nums[left]
,左边为有序nums[mid]<nums[left]
,右边为有序
var search = function(nums, target) {
let l = 0, r = nums.length - 1
while (l <= r) {
let mid = l + ((r - l) >> 1)
let midVal = nums[mid]
if (midVal === target) {
return mid
}
// 如果左边有序
if (midVal >= nums[l]) {
if (midVal > target && nums[l] <= target) {
r = mid - 1
} else {
l = mid + 1
}
continue
}
// 如果左边无序,则右边有序
if (midVal < target && nums[r] >= target) {
l = mid + 1
} else {
r = mid - 1
}
}
return -1
};
34. 在排序数组中查找元素的第一个和最后一个位置
- 依据题意,该数的第一个位置,必须满足以下条件,以target为6为例
- 这个位置左边必须小于8,或者左边没有数
- 该数的第二个位置,必须满足以下条件
- 这个位置右边必须大于8,或者右边没有数
var searchRange = function(nums, target) {
let l = 0, r = nums.length - 1
let leftIndex = rightIndex = -1
while (l <= r) {
let mid = l + ((r - l) >> 1)
if (nums[mid] === target) {
leftIndex = mid
r = mid - 1
} else if (nums[mid] > target) {
r = mid - 1
} else {
l = mid + 1
}
}
l = 0, r = nums.length - 1
while (l <= r) {
let mid = l + ((r - l) >> 1)
if (nums[mid] === target) {
rightIndex = mid
l = mid + 1
} else if (nums[mid] > target) {
r = mid - 1
} else {
l = mid + 1
}
}
return [leftIndex, rightIndex]
};
当然,以上解法是有些冗余的,为了找到左边界和右边界,分别去遍历,其实逻辑差不多,那么有没有办法优化呢,我们可以想到,首先,一次二分搜索肯定能找到等于target的值,找到后判断其左边和右边是否也等于target,然后更新左边界和右边界即可,代码如下
var searchRange = function(nums, target) {
let l = 0, r = nums.length - 1, mid
while (l <= r) {
mid = l + ((r - l) >> 1)
if (nums[mid] === target) {
break
} else if (nums[mid] > target) {
r = mid - 1
} else {
l = mid + 1
}
}
if (l > r) {
return [-1, -1]
}
let leftIndex = rightIndex = mid
while (nums[leftIndex - 1] === target) leftIndex--
while (nums[rightIndex + 1] === target) rightIndex++
return [leftIndex, rightIndex]
};