二分法:清晰与模糊的边界,一看就会,一写就废

前言

二分查找是一种在有序数组中高效查找特定元素的算法。它在计算机科学领域有着广泛的应用,是算法学习中的重要内容之一。

在实际问题中,我们经常需要在大量的数据中快速定位某个目标元素。二分查找算法凭借其高效的性能和简洁的思想,成为解决这类问题的有力工具。通过对二分查找算法的深入理解和掌握,我们能够更好地应对各种查找相关的挑战,提高程序的运行效率和准确性。

正文

题目(力扣704)

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
makefile 复制代码
示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
makefile 复制代码
示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
css 复制代码
提示:

你可以假设 nums 中的所有元素是不重复的。
n 将在 [1, 10000]之间。
nums 的每个元素都将在 [-9999, 9999]之间。

思路

要对一个所有元素都是不重复的并且是有序的数组进行操作。我们可以想到使用二分查找的方法解决这个算法题,因为二分法的前提条件就是数组为有序数组 并且没有重复元素。如果有重复元素就会使返回小标不唯一。

在想到使用二分法时,便感觉这题稳了。但是总是会因为一些小细节导致无法得到预期结果。

在循环过程中是用left<right进行判断还是用left<=right进行判断循环是否继续;在nums[mid]<target时是让left=mid还是让left=mid-1;当nums[mid]>target时是让right=mid还是让right=mid-1

要怎么解决呢?首先对二分查找的区间进行分类讨论,一般分为两类。

第一类

定义 target 是在一个[left,right]区间里。

第一个困扰:在循环过程中是用left<right进行判断还是用left<=right进行判断循环是否继续。

其中唯一的区别就是left=right是否有意义。在[left,right]区间,left=right是有意义的,所以选择left<=right

第二个困扰:在nums[mid]<target时是让left=mid还是让left=mid-1;当nums[mid]>target时是让right=mid还是让right=mid-1

在[left,right]区间里,nums[mid]<target

nums[mid]>target里的nums[mid]一定不等于target。所以应该选择为让left=mid-1和让right=mid-1

大体代码是这样的

javascript 复制代码
var search = function(nums, target) { 
    let left = 0; // 初始化左指针为 0
    let right = nums.length - 1; // 初始化右指针为数组的最后一个元素的索引
    let mid;

    while (left <= right) { // 只要左指针不超过右指针,就继续循环
        mid = Math.floor((right - left) / 2) + left; // 计算中间位置(可以防止溢出)
        if (nums[mid] === target) { // 如果中间位置的元素值等于目标值
            return mid; // 返回中间位置(即找到目标)
        } else if (nums[mid] > target) { // 如果中间位置的元素值大于目标值
            right = mid - 1; // 将右指针移动到中间位置的左侧
        } else { // 如果中间位置的元素值小于目标值
            left = mid + 1; // 将左指针移动到中间位置的右侧
        }
    }
    return -1; // 如果循环结束都没有找到目标值,返回 -1 表示未找到
};

第二类

定义 target 是在一个[left,right)区间里。

left=right在这个区间内并没有意义。所以用left<right对循环进行判断。

在[left,right)区间nums[mid]<target时,nums[mid]一定不等于target,所以让left=mid-1,但是因为右边为开区间所以在nums[mid]>target时,nums[mid]可能等于target,所以让right=mid

大体代码为

javascript 复制代码
var search = function(nums, target) { 
    // 初始化左指针为 0
    let left = 0; 
    // 初始化右指针为数组的长度(注意这里是 nums.length,而不是 nums.length - 1)
    let right = nums.length; 
    let mid; 

    while (left < right) { // 当左指针小于右指针时,继续循环
        mid = Math.floor((right - left) / 2) + left; // 计算中间位置(可以防止溢出)

        if (nums[mid] === target) { // 如果中间位置的元素等于目标值
            return mid; // 返回中间位置(即找到目标)
        } else if (nums[mid] > target) { // 如果中间位置的元素大于目标值
            right = mid; // 将右指针移动到中间位置
        } else { // 如果中间位置的元素小于目标值
            left = mid + 1; // 将左指针移动到中间位置的右侧
        }
    }
    return -1; // 如果循环结束都没有找到目标值,返回 -1 表示未找到
};
相关推荐
懒大王爱吃狼1 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
劲夫学编程1 小时前
leetcode:杨辉三角
算法·leetcode·职场和发展
毕竟秋山澪1 小时前
孤岛的总面积(Dfs C#
算法·深度优先
待磨的钝刨2 小时前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
浮生如梦_3 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
励志成为嵌入式工程师5 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
逐·風5 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法