当前位置: 首页 > article >正文

对二分搜索的理解 Go语言版

二分搜索大家都很熟悉,首先我们先来看看基本框架

func binarySearch(nums []int, target int) int {
    left, right := 0, ...

    for ... {
        mid := left + (right-left)/2
        if nums[mid] == target {
            ...
        } else if nums[mid] < target {
            left = ...
        } else if nums[mid] > target {
            right = ...
        }
    }
    return ...
}

看完代码之后我们会发现:

  1. 数组得是有序的才能成立
  2. 在存在两个及以上的答案时,只能找出一个,至于下标的情况我觉得可以自定义
  3. 如果数组长度为偶数,初始化时mid在中间偏左的那一格

接下来在实战中看:

寻找一个数

这可以说是最简单的内容

func binarySearch(nums []int, target int) int {
    left := 0 //
    right := len(nums) - 1 

    for left <= right {
        mid := left + (right - left) / 2 
        if nums[mid] == target { 
            return mid
        } else if nums[mid] < target { 
            left = mid + 1 // [mid + 1, right]
        } else if nums[mid] > target { // 目标值在左半部分,注意
            right = mid - 1 // [left, mid - 1]
        }
    }
    return -1 // 未找到
}

这里需要注意一点,为何是 for left <= right 而不是 for left < right

我们可以先从最极端的情况入手,假设要寻找的数在最后一个,那么在前一步应该是 left = right - 1, mid = left ,然后 left = mid + 1 = right,在进行循环时,循环就会进不去。

从理论上解释,因为在之前设定值时,数组为闭区间 [left, right] ,所以得保证边界值,也就是说,循环的条件设定和搜索区间设定是相关联的

寻找左侧边界

那么接下来看一下右侧开区间的做法:

func leftBound(nums []int, target int) int {
    left := 0
    right := len(nums)

    for left < right {
        mid := left + (right - left) / 2
        if nums[mid] == target {
            right = mid
        } else if nums[mid] < target {
            left = mid + 1 // [mid + 1, right)
        } else if nums[mid] > target {
            right = mid // [left, mid)
        }
    }
    return left
}

不难发现,随着区间的修改,在对应条件下的操作也会对应转换。

在这里阐述一点我的个人想法,二分查找的最大特点在于区间设定,而在对应条件下做出的操作有点像递归,基本盘并没有改变

寻找右侧边界

func right_bound(nums []int, target int) int {
    left, right := 0, len(nums)
    
    for left < right {
        mid := left + (right-left)/2
        if nums[mid] == target {   
            left = mid + 1        
        } else if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid
        }
    }
    return left - 1  
}

这里面的精华在于

if nums[mid] == target {   
	 left = mid + 1 

他并没有直接的收网,而是继续直到最右侧的诞生,这也得益于mid的设置,可以把他看成以left为基准向前进

实战

我们来看力扣的34. 在排序数组中查找元素的第一个和最后一个位置

直接把方法摆上既可以解决

func searchRange(nums []int, target int) []int {
    left := leftBound(nums, target)
    right := rightBound(nums, target)
    return []int{left, right}
}

func leftBound(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right - left) / 2
        if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid - 1
        } else if nums[mid] == target {
            // 别返回,锁定左侧边界
            right = mid - 1
        }
    }
    // 判断 target 是否存在于 nums 中
    if left < 0 || left >= len(nums) {
        return -1
    }
    // 判断一下 nums[left] 是不是 target
    if nums[left] == target {
        return left
    }
    return -1
}

func rightBound(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right - left) / 2
        if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid - 1
        } else if nums[mid] == target {
            // 别返回,锁定右侧边界
            left = mid + 1
        }
    }
    // 判断 target 是否存在于 nums 中
    // if left - 1 < 0 || left - 1 >= len(nums) {
    //     return -1
    // }

    if right < 0 || right >= len(nums) {
        return -1
    }
    if nums[right] == target {
        return right
    }
    return -1
}

当然也有另外一种解法,力扣显示这种时间复杂度更低
在这里插入图片描述


http://www.kler.cn/a/150221.html

相关文章:

  • 学webpack之loader原理,让面试官跪下来唱征服
  • Pycharm贪吃蛇小游戏后续2
  • pip install -r requirements.txt下载速度慢
  • Qt报错QOCI driver not loaded且QOCI available的解决方法
  • Zookeeper 简介 | 特点 | 数据存储
  • MFC工控项目实例二十八模拟量信号每秒采集100次
  • 从 Elasticsearch 到 SelectDB,观测云实现日志存储与分析的 10 倍性价比提升
  • 智能化质量控制,三坐标尺寸SPC管理系统引领制造新潮流!
  • sqli-labs靶场详解(less32-less37)
  • 什么是主机安全,有什么作用?
  • Android Studio Giraffe-2022.3.1-Patch-3安装注意事项
  • @Value和@ConfigurationProperties的区别,以及@ConfigurationProperties的配置依赖
  • 详解前后端交互时PO,DTO,VO模型类的应用场景
  • [论文阅读]CT3D——逐通道transformer改进3D目标检测
  • RK3568平台开发系列讲解(Linux系统篇)通过OF函数获取设备树节点实验
  • 云时空社会化商业 ERP 系统 service SQL 注入漏洞复现
  • mySQL踩坑记录
  • 【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷12
  • 从零构建属于自己的GPT系列1:文本数据预处理、文本数据tokenizer、逐行代码解读
  • SparkSQL远程调试(IDEA)
  • 深入了解Jackson库中的ObjectMapper:Java对象的序列化和反序列化
  • qt 简单了解QHBoxLayout QVBoxLayout QFormLayout水平,垂直,表单布局管理器.
  • BUUCTF刷题之路-pwn-ciscn_2019_n_81
  • Elasticsearch 聚合查询(Aggregation)详解
  • 虚拟机指定开放数据库3306端口
  • Golang开发之------ Beego框架