js实现二分查找

450 阅读1分钟

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.应用

  1. 二分插入排序

使用二分查找在有序序列中查找插入点,再插入

  1. 数字在排序数组中出现次数

右边界 - 左边界