【js刷题:数据结构数组篇之二分查找】

一、什么是二分查找法

二分查找法(Binary Search)是一种在有序数组中查找特定元素的算法。它通过将目标值与数组中间元素进行比较,从而排除掉一半的数据,以实现快速的搜索过程。

二、具体实现步骤

1.确定确定target所在数组的左右边界

写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)

左闭右闭

如果我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right]
那么遍历时的范围就应该是while (left <= right), 要使用 <= ,因为left == right是有意义

左闭右开

如果说定义 target 是在一个在左闭右开的区间里,也就是[left, right)
那么遍历时的范围就应该是while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义

2.取中间值

取数组的中间元素,与目标值进行比较。

左闭右闭

如果我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right]。假如说target的值小于数组中间的那个数,也就是if (nums[middle] > target) ,target在数组的左半部分,那么right 要赋值为 middle - 1,因为target肯定比原本的middle值要小,我们不需要再去比较一次i原本middle和target的值了,并且我们是定义的一个闭区间,所以right要赋值给middle-1,也就是说下次我们在[0,middle-1]这个闭区间里面继续找。

左闭右开

如果说定义 target 是在一个在左闭右开的区间里,假如说target的值还是小于数组中间的那个数,也就是if (nums[middle] > target) ,target在数组的左半部分,那么right 要赋值为 middle,因为target肯定比原本的middle值要小,我们也不需要再去比较一次i原本middle和target的值了,并且我们是定义的一个开区间,所以下次我们就在[0,middle)这个区间找。

3.中间元素=目标值

如果中间元素等于目标值,则找到了目标,算法结束。

4.中间元素大于目标值

如果中间元素大于目标值,则目标值只可能在左半部分,因此将搜索范围缩小为左半部分,再次进行二分查找。

5.中间元素小于目标值

如果中间元素小于目标值,则目标值只可能在右半部分,因此将搜索范围缩小为右半部分,再次进行二分查找。

6.重复

重复以上步骤,直到找到目标值或者确定目标值不在数组中。

三、使用条件

数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一。

四、js版本示例

1.左闭右闭

(right - left) >> 1 这个表达式是一个位运算操作。在这里,>> 是右移位运算符,将操作数向右移动指定的位数。

(right - left) 计算了右边界和左边界之间的距离。

>> 1 将这个距离向右移动一位,相当于除以2。这是因为在计算机中,右移一位相当于将数值除以2,并且效率更高。

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    // right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
    let mid, left = 0, right = nums.length - 1;
    // 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
    while (left <= right) {
        // 位运算 + 防止大数溢出
        mid = left + ((right - left) >> 1);
        // 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
        if (nums[mid] > target) {
            right = mid - 1;  // 去左面闭区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右面闭区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};

2.左闭右开

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    // right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
    let mid, left = 0, right = nums.length;    
    // 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
    while (left < right) {
        // 位运算 + 防止大数溢出
        mid = left + ((right - left) >> 1);
        // 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
        // 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
        if (nums[mid] > target) {
            right = mid;  // 去左区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};

五、力扣刷题

1.搜索插入位置

在这里插入图片描述

这道题在原有的基础上增添了一个新的条件:就是找不到元素时要返回元素本该插入的位置

为什么返回的值变成了right+1呢?

这是因为在循环过程中,我们通过不断更新 left 和 right 来缩小搜索范围,最终使得 left 大于 right。而 right 的值恰好是目标值在数组中最后一个小于目标值的元素的索引。所以,插入位置应该在 right 的右侧,即 right + 1 处。

例如,考虑一个有序数组 [1, 3, 5, 6],目标值是 4。在二分查找过程中,最终会有 left = 2 和 right = 1,即 right + 1 = 2,这表示目标值 4 应该插入到索引 2 的位置上。

因此,在这段代码中,返回的是 right + 1,表示目标值应该插入的位置。

2.在排序数组中查找元素的第一个和最后一个位置

在这里插入图片描述
这里我们分别查询target的左边界和有边界(不包含target值)
一共有三种查找情况:
在这里插入图片描述
下面是解题代码:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var searchRange = function (nums, target) {
    // 先找左指针
    const getLeftBorder = (nums, target) => {
        let left = 0, right = nums.length - 1;
        // leftBorder初始值,表示还未找到
        let leftBorder = -2;
        // 左闭右闭
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            // 表示target在左半部分
            if (nums[mid] >= target) {
                // 只要在从右往左的循环过程中比target值大或者等于target,right会不断缩小
                right = mid - 1;
                //right值就是target的左边界,不包含target
                leftBorder = right;
            }
            // target在右半部分,就更新一下left,在新的范围内查找左边界
            else {
                left = mid + 1;
            }
        }
        return leftBorder
    }
    const getRightBorder=(nums, target) => {
        let left = 0, right = nums.length - 1;
        let rightBorder = -2;
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            if (nums[mid] <= target) {
                left = mid + 1;
                rightBorder = left;
            } else {
                right = mid - 1;
            }
        }
            return rightBorder;
    }
    let leftBorder=getLeftBorder(nums,target);
    let rightBorder=getRightBorder(nums,target);
    //第一种情况:target值比数组中所有数都要小或者大,leftBorder和rightBorder其中一个是初始值-2
    if(leftBorder===-2 || rightBorder===-2) return [-1,-1];
    // 第二种情况就是target存在,右边界大于左边界
    if(rightBorder-leftBorder>1)return [leftBorder+1,rightBorder-1];
    //第三种情况是target 在数组范围中,且数组中不存在target
    return [-1,-1];


};

3.有效的完全平方根

在这里插入图片描述
当使用二分法来判断一个数是否为完全平方数时,我们将其平方根的搜索范围逐渐缩小,直到找到答案或者确定不存在答案。

具体来说,我们首先将搜索范围设置为从 0 到该数本身。然后,在每一次迭代中,我们计算当前搜索范围的中间值 mid,并计算 mid 的平方。接着,我们将根据 mid 的平方与目标数的大小关系,将搜索范围缩小为左半部分或右半部分,逐步逼近目标值。

如果 mid 的平方等于目标数,那么目标数就是完全平方数,直接返回 true。如果 mid 的平方大于目标数,我们将搜索范围缩小为左半部分(即将右边界调整为 mid - 1)。如果 mid 的平方小于目标数,我们将搜索范围缩小为右半部分(即将左边界调整为 mid + 1)。

通过这种方式,我们不断缩小搜索范围,最终要么找到目标值,要么确定目标值不存在。这就是二分法的思路。

4.x的平方根

在这里插入图片描述

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值