前端算法入门之路(十)(二分算法:致敬经典,超越经典)

318 阅读3分钟

二分查找算法

将n个元素分成大致相等的两部分,取a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果x>a[n/2],则只要在数组a的右半部搜索x

0-1模型

可以抽象为待查找元素是否满足查找条件,满足标记为1不满足标记为0

二分查找要保证待查找元素一定在待查找区间中

LeetCode肝题

    1. x 的平方根
// 在0和x+1之间查找第一个平方大于x的值,返回该值的下标-1
var mySqrt = function(x) {
    let head = 0, tail = x + 1, mid
    while(head < tail) {
        mid = parseInt((head + tail) / 2)
        if (mid * mid > x) tail = mid
        else if (mid * mid == x) return mid
        else head = mid + 1
    }
    return head - 1
};
    1. 搜索插入位置
// 在nums数组中查找第一个大于等于target的位置
var searchInsert = function(nums, target) {
    let head = 0, tail = nums.length, mid
    while(head < tail) {
        mid = (head + tail) >> 1
        if (nums[mid] >= target) tail = mid
        else head = mid + 1
    }
    return head
};
    1. 在排序数组中查找元素的第一个和最后一个位置
// 查两次,第一次查大于等于target的第一个位置,第二次查大于等于target+1的位置
var binary_search_01 = function(nums, target) {
    let head = 0, tail = nums.length, mid, ans = []
    while(head < tail) {
        mid = (head + tail) >> 1
        if (nums[mid] >= target) tail = mid
        else head = mid + 1
    }
    return head
}
var searchRange = function(nums, target) {
    let ans = [-1, -1]
    ans[0] = binary_search_01(nums, target)
    if (nums[ans[0]] != target) return [-1, -1]
    ans[1] = binary_search_01(nums, target + 1) - 1
    return ans
};
    1. 将 x 减到 0 的最小操作数
// 正向和反向求nums的两个前缀和数组,最小操作数其实就是两个前缀和数组相加之和等于x的下标之和
function binary_search(nums, target) {
    let head = 0, tail = nums.length - 1, mid
    while(head <= tail) {
        mid = (head + tail) >> 1
        if (nums[mid] == target) return mid
        else if (nums[mid] < target) head = mid + 1
        else tail = mid - 1
    }
    return -1
}
var minOperations = function(nums, x) {
    let suml = Array(nums.length + 1).fill(0), sumr = Array(nums.length + 1).fill(0), ans = -1
    for(let i = 0; i < nums.length; i++) suml[i+1] = nums[i] + suml[i]
    for(let i = nums.length - 1; i >= 0; i--) sumr[nums.length - i] = sumr[nums.length - i - 1] + nums[i]
    for(let i = 0; i < suml.length; i++) {
        let j = binary_search(sumr, x - suml[i])
        if (j == -1) continue
        if (i + j > nums.length) continue
        if (ans == -1 || ans > (i + j)) ans = i + j
    }
    return ans
};
    1. 供暖器
// 将供暖器数组排序,遍历房屋数组,二分查找找到大于等于该房屋的位置j,取j和j-1位置的供暖器与房屋的差值较小的最大值
var binary_search_01 = function(nums, target) {
    let head = 0, tail = nums.length - 1, mid
    while(head < tail) {
        mid = (head + tail) >> 1
        if (nums[mid] >= target) tail = mid
        else head = mid + 1
    }
    return head
}
var findRadius = function(houses, heaters) {
    heaters = heaters.sort((a, b) => a - b)
    let ans = 0
    for(let i = 0; i < houses.length; i++) {
        let j = binary_search_01(heaters, houses[i])
        let a = Math.abs(heaters[j] - houses[i])
        let b = j > 0 ? houses[i] - heaters[j - 1] : a + 1
        ans = Math.max(ans, Math.min(a, b))
    }
    return ans
};
    1. 搜索旋转排序数组 II
// 首先设置并调整头尾指针,让查找范围第一个元素大于最后一个元素
// 分为两种查找情况,第一种:mid位置在后面的递增区间,如果target大于中间值小于tail值,调整l到mid+1,否则调整r到mid-1
// 第二种:mid位置在前面的递增区间,如果target小于中间值大于head值,调整r到mid-1,否则调整l到mid+1
var search = function(nums, target) {
    if (nums[0] == target || nums[nums.length - 1] == target) return true
    let l = 0, r = nums.length - 1, mid, head, tail = r
    while(nums[l] == nums[r] && l < r) ++l
    head = l
    while(l <= r) {
        mid = (l + r) >> 1
        if (nums[mid] == target) return true
        if (nums[mid] <= nums[tail]) {
            if (target > nums[mid] && target <= nums[tail]) l = mid + 1
            else r = mid - 1
        } else {
            if (target < nums[mid] && target >= nums[head]) r = mid - 1
            else l = mid + 1
        }
    }
    return false
};
    1. 寻找两个正序数组的中位数
// 有点变态
// 在两个有序数组中寻找第k个元素
var findK = function(nums1, nums2, i, j, k) {
    if (i == nums1.length) return nums2[j + k - 1]
    if (j == nums2.length) return nums1[i + k - 1]
    if (k == 1) return nums1[i] < nums2[j] ? nums1[i] : nums2[j]
    let a = Math.min(k >> 1, nums1.length - i)
    let b = Math.min(k - a, nums2.length - j)
    a = k - b
    if (nums1[i + a - 1] <= nums2[j + b - 1]) {
        return findK(nums1, nums2, i + a, j, k - a)
    }
    return findK(nums1, nums2, i, j + b, k - b)
}
var findMedianSortedArrays = function(nums1, nums2) {
    let n = nums1.length, m = nums2.length, mid = (n + m + 1) >> 1
    let a = findK(nums1, nums2, 0, 0, mid)
    if ((n + m) % 2 == 1) return a
    let b = findK(nums1, nums2, 0, 0, mid + 1)
    return (a + b) / 2
};