二分查找

92 阅读1分钟
  1. 寻找无重复旋转数组中数字的位置

tip: 循环不变量:目标数字永远出现在[start, end]之间

解法: 比较middle和start. 如果middle大于等于start, 那么middle在前半段。反之后半段。

如果middle大于等于start: target大于等于start且小于middle,那么前半段。反之后半段。

如果middle小于start: target大于middle且小于等于end,那么后半段。否则前半段。

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let start = 0, end = nums.length - 1;
    while (start <= end) {
        let middle = Math.floor((start + end) / 2);
        if (target == nums[middle]) return middle;
        if (nums[middle] >= nums[start]) {
            if (target >= nums[start] && target < nums[middle]) {
                end = middle - 1;
            } else {
                start = middle + 1;
            }
        } else {
            if (target > nums[middle] && target <= nums[end]) {
                start = middle + 1;
            } else {
                end = middle - 1;
            }
        }
    }

    return -1;
};
  1. 还能以寻找目标为二分查找的范围对象

  2. 寻找峰值

tip: 根据middle处于上升周期还是下降周期判断

/**
 * @param {number[]} nums
 * @return {number}
 */
//循环不变量:要查找的数永远在[start, end]内
var findPeakElement = function(nums) {
    if (nums.length == 1) return 0;

    let start = 0, end = nums.length - 1;
    while (start <= end) {
        let middle = Math.floor((start + end) / 2);    
        if (middle == 0) {
            if (nums[middle] > nums[middle + 1]) {
                return middle;
            } else {
                start = middle + 1;
            }
        } else if (middle == nums.length - 1) {
            if (nums[middle] > nums[middle - 1]) {
                return middle;
            } else {
                end = middle - 1;
            }
        } else {
            if (nums[middle] > nums[middle - 1] && nums[middle] > nums[middle + 1]) {
                return middle;
            } else if (nums[middle] < nums[middle + 1]) {
                start = middle + 1;
            } else {
                end = middle - 1;
            }
        }
    }
    return -1;
};
  1. 包含重复数字的旋转数组二分查找
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {boolean}
 */
var search = function(nums, target) {
    let start = 0, end = nums.length - 1;

    //循环不变量为,要找的值总在[start, end]
    while (start <= end) {
        let middle = Math.floor((start + end) / 2);
        if (nums[middle] == target) return true;
        //middle一定在左半边
        if (nums[middle] > nums[start]) {
            if (target >= nums[start] && target < nums[middle]) {
                end = middle - 1;
            } else {
                start = middle + 1;
            }
        //middle一定在右半边
        } else if (nums[middle] < nums[start]) {
            //target也在右半边
            if (target > nums[middle] && target <= nums[end]) {
                start = middle + 1;
            } else {
                end = middle - 1;
            }
        //middle恰好等于start,两种可能,一种是在左边,一种是旋转到了右边
        //那么这时候,判断它是不是一定在左边
        } else {
            //Middle有可能左有可能右可左可右
            if (nums[start] == nums[end]) {
                //把头部和尾部重复的数字都去掉,反正都不等于target
                //middle如果在旋转到右边的部分,那么目标在[start, middle - 1]
                //middle如果在保留在左侧的部分,那么目标在[middle + 1, end]
                while (start + 1 <= end && nums[start + 1] == nums[start]) start++;
                //没有其他数字,return false
                start++;
                if (start >= end) return false;
                while (end - 1 >= start && nums[end - 1] == nums[end]) end--;
                end--;
            //middle一定在左边
            } else {
                start = middle + 1;
            }
        }
    }

    return false;
};
  1. 排序数组中找到距离某个数x最近的k个数。

解法:找到x的位置,不断向两侧双指针扩散,直到大小为k

/**
 * @param {number[]} arr
 * @param {number} k
 * @param {number} x
 * @return {number[]}
 */
var binarySearch = function(arr, start, end, target) {

    if (arr[start] >= target) return start - 1;
    if (arr[end] <= target) return end + 1;
    
    while(start <= end) {
        let middle = Math.floor((start + end) / 2);
        if (arr[middle] <= target && arr[middle + 1] >= target) {
            return middle + 1;
        } else if (arr[middle] < target) {
            start = middle + 1;
        } else {
            end = middle - 1;
        }
    }

}


var findClosestElements = function(arr, k, x) {
    //找到x的位置
    let targetPos = binarySearch(arr, 0, arr.length - 1, x);
    //如果等于-1, 证明小于最小值
    if (targetPos == -1) {
        return arr.slice(0, k);
    //等于len,证明大于最大值
    } else if (targetPos == arr.length) {
        return arr.slice(arr.length - k, arr.length);
    } else {
        let i = targetPos - 1, j = targetPos, result = [];
        while(k > 0 && i >= 0 && j < arr.length) {
            if (Math.abs(arr[i] - x) <= Math.abs(arr[j] - x)) {
                result.unshift(arr[i]);
                i--;
            } else {
                result.push(arr[j]);
                j++;
            }
            k--;
        }

        while (k > 0) {
            if (i == -1) {
                while(k > 0) {
                    result.push(arr[j]);
                    j++;
                    k--;
                }
            } else if (j == arr.length) {
                while(k > 0) {
                    result.unshift(arr[i]);
                    i--;
                    k--;
                }
            }
        }

        return result;
    }
};