二分法又可以被称为二分查找,它描述了在有序集合中搜索特定值的过程。广义的二分查找是将问题的规模尽可能的缩小到原有的一半。
对于二分法的思想大家都能讲出几句,但我们仍然很难讲其与实际应用完美的结合到一起,所以我们尽量汇总二分法的应用场景,和大家一起深入,共勉!
给定一个由数字组成的有序数组 nums
,并给你一个数字 target
。问 nums
中是否存在 target
。如果存在,则返回其在 nums
中的索引。如果不存在,则返回 - 1。
这是二分查找中最简单的一种形式。当然二分查找也有很多的变形,这也是二分查找容易出错,难以掌握的原因。
常见变体有:
如前所述,二分查找是一种在每次比较之后将查找空间一分为二的算法。每次需要查找集合中的索引或元素时,都应该考虑二分查找。如果集合是无序的,我们可以总是在应用二分查找之前先对其进行排序。但要注意排序的成本
target
: 要查找的值index
: 查找时的当前位置left
和 right
: 左右指针mid
: 左右指针的中点,用来确定我们应该向左查找还是向右查找的索引在最简单的形式中,二分查找对具有指定左索引和右索引的连续序列进行操作。这就是所谓的查找空间。二分查找维护查找空间的左、右和中间指示符,并比较查找目标或将查找条件应用于集合的中间值;如果条件不满足或值不相等,则清除目标不可能存在的那一半,并在剩下的一半上继续查找,直到成功为止。如果查以空的一半结束,则无法满足条件,并且无法找到目标。
注意:
看过之后的几个模版,并结合配套的例题,相信你会有自己的想法。
模版 I 是基础的二分查找,用于查找可以通过访问数组中的单个索引来确定的元素或条件。
O(logN)
O(logN)
O(1)
left = 0
right = arrar.length-1 或 number
left > right
right = mid-1
left = mid+1
var search = function(nums, target) {
left = 0; // 初始左边界
right = nums.length - 1; // 初始右边界
while (left <= right) {
let mid = left + Math.floor((right - left) / 2); //防止溢出
if (nums[mid] < target) {
// 收缩左边界
left = mid + 1;
} else if (nums[mid] > target) {
// 收缩右边界
right = mid - 1;
} else {
return mid; // 找到了当前值
}
}
return -1;
};
left
: 0right
: nums.length-1mid
: (left + (right - left) >> 1)nums[mid]
< target:证明中间值左侧包括中间值都不符合要求,可以直接排除,left = mid + 1
nums[mid]
:证明中间值右侧包括中间值都不符合要求,可以直接排除,right = mid - 1
nums[mid]
= target:直接返回mid
的下标left <= right
下面五题,难度递增,每篇题解都包含原题链接、模版分析、Js 题解,大家可以放心食用,我们一起加油~
模板 II 是二分查找的高级模板。它用于查找需要访问数组中当前索引及其直接右邻居索引的元素或条件。
var search = function(nums, target) {
left = 0; // 初始左边界
right = nums.length - 1; // 初始右边界
while (left <= right) {
var mid = left + Math.floor(left + (right - left) / 2); //防止溢出
if (target == nums[mid]) {
right = mid + 1; // 继续寻找左侧是否仍存在满足条件的元素,但包含当前元素
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
if (left != nums.length && nums[left] === target) return left;
return -1;
};
首先定义搜索区间为 [left, right],注意是左右都闭合,之后会用到这个点。
下面 3 题,难度递增,每篇题解都包含原题链接、模版分析、Js 题解,大家可以放心食用,我们一起加油~
模板 III 是二分查找的另一种独特形式。它用于搜索需要访问当前索引及其在数组中的直接左右邻居索引的元素或条件。
2
个元素时,循环 / 递归结束。需要评估其余元素是否符合条件。left = 0, right = length - 1
left + 1 === right
right = mid
left = mid
function binarySearch(nums, target) {
const len = nums.length;
if (len === 0) return -1;
let left = 0;
let right = len - 1;
while (left + 1 < right) {
let mid = left + ((right - left) >> 1);
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;
}
因为我们的判断区间最少为 2 个元素,所以我们要注意循环的执行条件
nums.length === 0,return -1
;left = 0, right = nums.length - 1
;left + 1 < right
,这也意味着查找区间要存在 3 个元素;right = mid
;left = mid
;下面 3 题,难度递增,每篇题解都包含原题链接、模版分析、Js 题解,大家可以放心食用,我们一起加油~
本小结引自于LeetCode二分专题
上面提到的三个模版,大家在做题的时候很难完全套用进去,大部分时候,都是寻找最合适的方式并不断调整
这 3 个模板的不同之处在于:
个人感觉模版I最实用(其实是另外两个模版掉头发啊!)
模板I (left <= right)
二分查找的最基础和最基本的形式。
模板II (left < right)
一种实现二分查找的高级方法。
模板III (left + 1 < right)
实现二分查找的另一种方法。
本文解释了三种最常见的解题模版,并附上LeetCode实战,帮助我们理解二分法这种思想。
如果对你有所帮助不妨给本项目的github 点个 star,这是对我最大的鼓励
关于我
其他沉淀