二分查找
二分查找是一种基于比较目标值和数组中间元素的教科书式算法。
- 如果目标值等于中间元素,则找到目标值。
- 如果目标值较小,继续在左侧搜索。
- 如果目标值较大,则继续在右侧搜索。
算法步骤:
- 初始化指针 left = 0, right = n - 1。
- 当 left <= right: 比较中间元素 nums[pivot]和目标值 target 。
- 如果 target = nums[pivot],返回 pivot。
- 如果 target <nums[pivot],则在左侧继续搜索 right = pivot - 1。
- 如果 target >nums[pivot],则在右侧继续搜索 left = pivot + 1。
作者:LeetCode
链接:https://leetcode-cn.com/problems/binary-search/solution/er-fen-cha-zhao-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
标准二分法问题
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
第一种解法:暴力循环
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
var flag = 1;
var index = undefined
for(var i = 0; i < nums.length; i++){
if(nums[i] == target){
flag = 0;
index = i;
break
//return i
}else{
continue
}
}
if(!flag){
return index
}
else{
return -1
}
};
仔细体会一下这里 flag 的用处,这种用法也是我在查看其他题解的过程中学习的,对于需要判断标志的情形均可以使用
第二种解法:二分法
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
var first = 0;
var last = nums.length - 1;
while(first <= last){
var mid = Math.floor((first + last) /2);
if( nums[mid] < target){
first = mid + 1;
}
if(nums[mid] == target){
return mid ;
}
if(nums[mid] > target){
last = mid - 1;
}
}
return -1;
};```
```javascript
在这里插入代码片
二分法变形——第一题:
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/first-bad-version
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* Definition for isBadVersion()
*
* @param {integer} version number
* @return {boolean} whether the version is bad
* isBadVersion = function(version) {
* ...
* };
*/
/**
* @param {function} isBadVersion()
* @return {function}
*/
var solution = function(isBadVersion) {
/**
* @param {integer} n Total versions
* @return {integer} The first bad version
*/
return function(n) {
// 代码写在这里
var left = 1;
var right = n;
while(left <= right){
var mid = Math.floor((left + right)/2);
if(isBadVersion(mid) == false){
// 错误版本在 mid 后面,包含 mid
if(isBadVersion(mid + 1) == true){
return mid + 1
}
else{
//排除 mid 的情形,也就是 错误版本在 mid 后面
left = mid + 1;
}
}
if(isBadVersion(mid) == true){
// 错误版本在 mid 之前,包含 mid
if(isBadVersion(mid - 1) == false){
return mid
}
else{
//排除 mid 的情形,也就是 错误版本在 mid 前面
right = mid - 1;
}
}
}
};
};
二分法变形——第二题:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-insert-position
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
var left = 0;
var right = nums.length - 1;
var ans = nums.length;
while(left <= right){
var mid = Math.floor(left + (right - left)/2);
if (target <= nums[mid]) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
};
总结
对于二分法问题:
- 首先肯定是要知道这个问题是二分法问题;
- 关于区间的选择问题,是[left,right]还是[left,right),推荐使用闭区间,比较简单;原因有二:其一是使用左闭右开的区间,在设置while循环的判断条件时有所不同。其二是mid包含在哪个部分,就在哪个需要单独讨论 mid 的情况;
- 关于 mid 的求解过程,以上两种变形写出了两种方式:其一是left + (right - left)/2);另一种是(left + right)/2,从数学角度上来说,两种并无区别,我自己也并没有发现区别,但是在写程序时,写 ** (left + right)/2** 偶尔会报错,查看题解时,大家都这么写left + (right - left)/2)。说法是left + right会导致溢出(超过数组索引值范围)。推荐写法:left + (right - left)/2)。
不足之处欢迎大家批评指正。