1.题目
请实现有重复数字的升序数组的二分查找
给定一个元素有序的(升序)长度为n的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的第一个出现的target,如果目标值存在返回下标,否则返回 -1
数据范围:1<n<10000
示例 1:
输入:[1,2,4,4,5],4
返回值:2
说明:从左到右,查找到第1个为4的,下标为2,返回2
示例 2:
输入:[1,2,4,4,5],3
返回值:-1
示例 3:
输入:[1,1,1,1,1],1
返回值:2
提示:
1.首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则进行下一步。
2.如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。
3.如果某一步数组为空,则表示找不到目标元素。
2.解决思路
方法一:非递归,while循环
取中间值:parseInt((high + low) / 2);
low,high为区域首尾元素的索引;
结束循环的条件:low > high。
方法二:递归算法,同理
3.实现代码
// 非递归算法
function binary_search(arr, key) {
var low = 0
var high = arr.length - 1;
while(low <= high) {
var mid = parseInt((high + low) / 2);
if(key == arr[mid]) {
return mid;
} else if(key > arr[mid]) {
// 目标元素大于中间值,取右边
low = mid + 1;
} else if(key < arr[mid]) {
// 目标元素小于中间值,取左边
high = mid -1;
}
}
return -1;
};
var arr = [1,2,3,4,5,6,7,8,9,10,11,23,44,86];
var result = binary_search(arr,10);
alert(result); // 9 返回目标元素的索引值
// 递归算法
function binary_search(arr,low, high, key) {
if (low > high) {
return -1;
}
var mid = parseInt((high + low) / 2);
if(arr[mid] == key) {
return mid;
}else if (arr[mid] > key) {
high = mid - 1;
return binary_search(arr, low, high, key);
}else if (arr[mid] < key) {
low = mid + 1;
return binary_search(arr, low, high, key);
}
};
var arr = [1,2,3,4,5,6,7,8,9,10,11,23,44,86];
var result = binary_search(arr, 0, 13, 10);
alert(result); // 9 返回目标元素的索引值
时间复杂度:
总共有n个元素,循环k次,n,n/2,n/4,....n/2^k,因为n/2^k>=1,则令n/2^k=1,可得k=log2n,(是以2为底,n的对数)
O(log2n) => O(logn)
4.扩展
局限性:
有序数组 nums = [1,2,2,2,3],target = 2,此算法返回的索引是 2
如果想得到 target 的左侧边界,即索引 1,或者想得到 target 的右侧边界,即索引 3,以上算法是无法处理的。
如果先找到一个 target 索引,然后向左或向右线性搜索,可以实现,但是难以保证二分查找对数级的时间复杂度
寻找左侧边界的二分搜索
function left_bound(nums, target) {
let left = 0;
let right = nums.length;
while (left < right) {
const mid = parseInt((left + right) / 2);
if (nums[mid] === target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return nums[left] === target ? left : -1;
}
寻找右侧边界的二分搜索
function right_bound(nums, target) {
let left = 0;
let right = nums.length;
while (left < right) {
const mid = parseInt((left + right) / 2);
if (nums[mid] === target) {
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return nums[left - 1] === target ? left - 1 : -1;
}
5.应用
- 二分插入排序
使用二分查找在有序序列中查找插入点,再插入
- 数字在排序数组中出现次数
右边界 - 左边界