二分法总结自用

找数组中特定的某个值

function find(nums, target) {
    let left = 0, right = nums.length - 1
    while (left <= right) {
        const mid = Math.floor((left+right)/2)
        if(nums[mid] === target) {
            return mid
        } else if(nums[mid] > target) {
            right = mid - 1
        } else {
            left = mid + 1
        }
    }
    return -1
}

找左边界

方法一:

/** 
 * 找左边界,
 * 例:给定一个按升序排列的数组,找到小于x的第一个数 
 * 注意:数字可能重复,且x不一定存在在数组内
 **/
function findLeftBorder(nums, x) {
    let left = 0, right = nums.length - 1
    while (left <= right) {
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] >= x) {
            right = mid - 1
        } else {
            left = mid + 1
        }
    }
    // 超出边界就是-1不用额外处理
    return right
}

方法二:

和方法一的区别在于目标值的有效区间,方法一每次while判断[left, right]都是有效值,所以判断条件需要是left<=right。left和right向目标值收敛。注意每次迭代时right和left都不能是mid,否则可能会出现nums[left]===nums[right]===num[mid]导致无法继续收敛而无限循环。

而方法二的有效区间是[left, right),所以跳出条件是left === right。也因此下面的代码在mid指向的值大于目标值时直接给right赋了mid,因为给right赋mid值的有效区间是[left, mid-1]。如果赋mid-1会包含一个无效值nums[mid]。

function findLeftBorder_2(nums, x) {
    let left = 0, right = nums.length
    // 迭代范围[left, right)
    while (left < right) {
        // 通过这种方式取中间值左边值能取到,但右边值不一定
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] === x) {
            left = mid // 需要把mid包含在下一次迭代的[left, right)范围内
        } else if (nums[mid] < x) {
            left = mid + 1 // 目标值属于[mid + 1, right)
        } else {
            right = mid // nums[mid] > x 时目标值属于[left, mid - 1), 但right值不在迭代范围内,所以不用-1
        }
    }
    return left
}

找右边界

方法一:

/** 
 * 找右边界,
 * 例:给定一个按升序排列的数组,找到大于的第一个数 
 * 注意:数字可能重复,且x不一定存在在数组内
 **/
function findRightBorder(nums, x) {
    // 取到nums.length VS 取到nums.length - 1
    // 取到nums.length - 1时右边界是有效的范围,while条件也需要等号,这个时候即使mid的值是预期值也要-1,否则可能无法退出迭代(left === right === 返回值)
    let left = 0, right = nums.length - 1
    while (left <= right) {
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] <= x) {
            // 处理后的跳出的left要么满足要求,要么超出数组边界
            left = mid + 1// 此时mid不是个可能的值,目标值的范围在 [mid+1,right]
        } else {
            right = mid - 1//nums[mid] > x, 虽然此时mid是一个可能值,但如果不-1,left和right的取值范围都包含mid,无法跳出循环
        }
    }
    // 处理数组中的数字都小于等于x的情况,根据题目实际需要调整
    return left >= nums.length ? -1 : left
}

方法二:

function findRightBorder_2(nums, x) {
    if (nums.length <= 0) return -1
    let left = 0, right = nums.length
    while (left < right) {
        const mid = Math.floor((left + right) / 2)
        if (nums[mid] === x) {
            left = mid + 1
        } else if (nums[mid] > x) {
            right = mid //此时mid是个可能的值,但在left的取值范围内,right就不用考虑了
        } else {
            left = mid + 1 //nums[mid] < x 此时mid不是个可能的值,目标值的范围在 [mid+1,right]
        }
    }
    return left
}

leetcode题:

  1. 二分查找
  2. 在排序数组中查找元素的第一个和最后一个位置
  3. 搜索旋转排序数组
  4. 寻找旋转排序数组中的最小值
  5. 寻找峰值
  6. 长度最小的子数组
  7. 找到 K 个最接近的元素

学习资源:

  1. 力扣
  2. 二分法的细节加细节 你真的应该搞懂!!!_二分法为什么要加一-CSDN博客
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值