二分的主要细节在于左右区间的选择。 如leetcode704.
[704] Binary Search
var search = function(nums, target) {
let start = 0;
let end = nums.length - 1;
while(start <= end){
let mid = start + Math.floor((end - start)/2);
let midVal = nums[mid];
if(target === midVal) return mid;
if(target < midVal){
end = mid - 1;
}else{
start = mid + 1;
};
};
return -1;
};
主要的要点在于要清楚查找的区间。这里是一个前闭合后闭合。
左侧边界查找
除了基本的排序不重复数组中查找值,还有一种在排序重复数组中查找值的左右边界。 如[1,2,3,3,3,3,4,5]数组,给定值3,求查找出该数组中第一次出现3这个值的位置。该种查找叫做左侧边界查找。这种做法可以选择二分,也可以选择遍历。遍历的时间复杂度过高,要O(n)。
var search = function(nums,target){
let left = 0;
let right = nums.length;
//采用左闭右开的区间
while(left < right){
let mid = left + Math.floor((right - left)/2);
if(nums[mid] === target){//如果相等,收缩右边界
right = mid;
}else if(nums[mid] < target){//如果target > nums[mid],左边界+1
left = mid + 1;
}else if(nums[mid] > target){ //如果target < nums[mid],收缩右边界
right = mid;
};
}
return left;
}
右侧边界查找
同样是采用左闭右开,别的地方都容易推理,主要是返回值与之前不同。因为是左闭右开区间,并且整体是左区间在收缩,因此要找的值会在left和right的左侧,因此要减1.
var right_bound = function(nums, target) {
if(nums.length === 0) return -1;
let left = 0,right = nums.length;
//采用左闭右开的区间
while(left < right){
let mid = Math.floor((left + right) / 2);
if(nums[mid] === target){
left = mid + 1; //如果相等,收缩左边界
}else if(nums[mid] < target){
left = mid + 1; //target大于nums[mid],收缩左边界
}else if(nums[mid] > target){
right = mid; //target小于nums[mid],收缩右边界
}
};
return left - 1;//返回右边界
}
变种:搜索旋转数组中的某个值
leetcode的原题:搜索旋转排序数组
本质上仍然是基础的二分查找,只是旋转数组中,存在一个大段和小段的问题,,因此多了一步中点值位于哪一块的问题。
var search = function(nums, target) {
let start = 0; let end = nums.length - 1;
while(start <= end){ //基础的二分查找
let mid = start + Math.floor((end - start)/2);
if(nums[mid] === target) return mid; //查找到结果返回
if(nums[start] <=nums[mid]){ //中点值位于大段
if(target >= nums[start] && target < nums[mid]){
end = mid - 1;
}else {
start = mid + 1;
}
}
else if(nums[start] > nums[mid]){ //中点值位于小段
if(target > nums[mid] && target <= nums[end]){
start = mid + 1;
}else {
end = mid - 1;
}
}
}
return -1;
};
变种:搜索旋转数组中的某个值II
leetcode的原题: 搜索旋转排序数组II
本题是上一题的变种,多了一个重复数字,因此在判断大小段的时候,不能使用<= 。对于==的情况要特殊另外处理。
var search = function(nums, target) {
let start = 0 ;
let end = nums.length - 1;
while(start <= end){
let mid = start + Math.floor((end - start)/2);
if(target === nums[mid]) return true;
if(nums[start] < nums[mid]){
if(target >= nums[start] && target < nums[mid]){
end = mid - 1;
}else{
start = mid + 1;
}
}else if (nums[start] > nums[mid]){
if(target> nums[mid] && target <= nums[end] ){
start = mid + 1;
}else {
end = mid - 1;
}
}else if(nums[start] === nums[mid]){ //如果等于,就往里缩,因为要找的值必定只能在区间内部。
start++;
};
};
return false;
};