二分查找法总结

news2024/9/25 21:23:18

目录

  • 1、思路讲解(LC704)
  • 2、代码思路讲解(循环不变量)
    • (1) 左闭右闭
    • (2)左闭右开
    • (3)总结:左开右闭和左闭右开
    • (4)复杂度分析
  • 3. 习题分析
    • (1)LC35 搜索插入位置 easy (二分查找法变种问题)
      • 思路
      • 代码
    • (2)LC34 在排序数组中查找元素的第一个和最后一个位置 medium(有重复元素的情况)
      • 思路1:二分查找+线性遍历
      • 思路2:扩展二分查找

1、思路讲解(LC704)

LC704:给定一个长度为 n n n的数组 nums ,元素按从小到大的顺序排列且不重复。请查找并返回元素 target 在该数组中的索引。若数组不包含该元素,则返回-1。
在这里插入图片描述

暴力法思路: n u m s [ 0 ] nums[0] nums[0]开始遍历一遍,time complexity = O ( n ) O(n) O(n)
二分法思路:
☀️首先要保证原序列是排好顺序的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、代码思路讲解(循环不变量)

伪代码:

def func(nums , target) -> int:
    # 初始化首元素、尾元素
    left = 0
    right = len(nums) - 1 or len(nums)
    # 循环
    while 满足左指针在右指针的左边:
        # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题
        m = (i + j) // 2  # 计算中点索引 m
        if nums[m] < target:
           # 说明target在nums[m]的右侧
           移动left
        elif nums[m] > target:
            # 说明target在nums[m]的左侧
        else:
            return m  # 找到目标元素,返回其索引
    return -1  # 未找到目标元素,返回 -1

这里面有几个很容易出错的点(会导致循环不收敛
):

  • while的循环条件是: l e f t < = r i g h t left<=right left<=right or l e f t < r i g h t left<right left<right
  • 当nums[mid]<target的时候,应该是 l e f t = m i d + 1 left = mid+1 left=mid+1 or l e f t = m i d left = mid left=mid
  • 当nums[mid]>target的时候,应该是 r i g h t = m i d − 1 right = mid - 1 right=mid1 or r i g h t = m i d right = mid right=mid
    这几个问题的答案是:你定义的区间是什么样子的?

(1) 左闭右闭

如果定义的区间是左闭右闭的情况: [ l e f t , r i g h t ] [left,right] [left,right]

  • while的循环条件是: l e f t < = r i g h t left<=right left<=right (⭐️最推荐的选择,so easy)
    • 因为当 l e f t = r i g h t left=right left=right 的时候, [ l e f t , r i g h t ] [left,right] [left,right]区间中仍然有一个元素,所以仍然是合法的
  • 当nums[mid]<target的时候,应该是 l e f t = m i d + 1 left = mid+1 left=mid+1
    • 因为当nums[mid]<target的时候,就证明了mid指向的值一定不是目标值,所以left不应该指向mid,而应该是mid+1
  • 当nums[mid]>target的时候,应该是 r i g h t = m i d − 1 right = mid - 1 right=mid1 or r i g h t = m i d right = mid right=mid
def binary_search(nums: list[int], target: int) -> int:
    """二分查找(双闭区间)"""
    # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素
    i, j = 0, len(nums) - 1
    # 循环,当搜索区间为空时跳出(当 i > j 时为空)
    while i <= j:
        # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题
        m = (i + j) // 2  # 计算中点索引 m
        if nums[m] < target:
            i = m + 1  # 此情况说明 target 在区间 [m+1, j] 中
        elif nums[m] > target:
            j = m - 1  # 此情况说明 target 在区间 [i, m-1] 中
        else:
            return m  # 找到目标元素,返回其索引
    return -1  # 未找到目标元素,返回 -1

(2)左闭右开

如果定义的区间是左闭右开的情况: [ l e f t , r i g h t ) [left,right) [left,right)

  • while的循环条件是: l e f t < r i g h t left<right left<right
    • 因为当 l e f t = r i g h t left=right left=right 的时候, [ l e f t = r i g h t , r i g h t ) [left=right,right) [left=right,right)区间就会既有right又没有right,这种情况显然是不合法的
  • 当nums[mid]<target的时候,应该是 l e f t = m i d + 1 left = mid+1 left=mid+1
    • 因为当nums[mid]<target的时候,就证明了mid指向的值一定不是目标值,所以left不应该指向mid,而应该是mid+1
  • 当nums[mid]>target的时候,应该是 r i g h t = m i d right = mid right=mid
    • 因为区间是 [ l e f t , r i g h t ) [left,right) [left,right),当mid的值不是目标值的时候,区间应该是mid值前面的序列,但是因为右区间是开区间,所以可以直接将right指向mid。
def binary_search_lcro(nums: list[int], target: int) -> int:
    """二分查找(左闭右开区间)"""
    # 初始化左闭右开区间 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1
    i, j = 0, len(nums)
    # 循环,当搜索区间为空时跳出(当 i = j 时为空)
    while i < j:
        m = (i + j) // 2  # 计算中点索引 m
        if nums[m] < target:
            i = m + 1  # 此情况说明 target 在区间 [m+1, j) 中
        elif nums[m] > target:
            j = m  # 此情况说明 target 在区间 [i, m) 中
        else:
            return m  # 找到目标元素,返回其索引
    return -1  # 未找到目标元素,返回 -1

(3)总结:左开右闭和左闭右开

在这里插入图片描述

(4)复杂度分析

时间复杂度: O ( l o g n ) O(logn) O(logn) 每次循环区间减少一半,因此循环次数是 O ( l o g n ) O(logn) O(logn)
空间复杂度: O ( 1 ) O(1) O(1)没用使用数组啥的

3. 习题分析

(1)LC35 搜索插入位置 easy (二分查找法变种问题)

LC35:给定一个长度为 n n n的数组 nums ,元素按从小到大的顺序排列且不重复。给一个元素target,想要插入到nums中,并保持有序性。如果数组中存在target,就将targat插入到左侧;如果不存在,将target插入到按顺序插入的位置,返回索引。
在这里插入图片描述

思路

⭐️思考:
Q1: 当数组中有target的时候,插入点的索引是否就是返回值?
回答: yep!当查找到原数组有target值时,新的target要放在老的target的左侧,也就是说新的target代替了老的target的位置,也就是插入点的索引就是新插入的target的索引
Q2: 当数组不存在target的时候,新插入点是哪个元素的索引?
在这里插入图片描述

代码

def binary_search_insertion_simple(nums: list[int], target: int) -> int:
    """二分查找插入点(无重复元素)闭区间"""
    i, j = 0, len(nums) - 1  # 初始化双闭区间 [0, n-1]
    while i <= j:
        m = (i + j) // 2  # 计算中点索引 m
        if nums[m] < target:
            i = m + 1  # target 在区间 [m+1, j] 中
        elif nums[m] > target:
            j = m - 1  # target 在区间 [i, m-1] 中
        else:
            return m  # 找到 target ,返回插入点 m
    # 未找到 target ,返回插入点 i
    return i

(2)LC34 在排序数组中查找元素的第一个和最后一个位置 medium(有重复元素的情况)

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
在这里插入图片描述

思路1:二分查找+线性遍历

1️⃣ 执行二分查找,得到任意一个 target 的索引
2️⃣从找到的这个索引开始,分别向左和向右遍历,找到start和end指针

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if nums is None or len(nums) == 0:
            return [-1,-1]
        # 先用二分查找法找到target
        # 双闭区间(有重复元素)
        left = 0
        right = len(nums) - 1
        flag = 0
        while left <= right:
            mid = left + (right - left) // 2
            if target > nums[mid]: # 应该删除前半部分的元素
                left = mid + 1
            elif target < nums[mid]: # 应该删除后半部分的元素
                right = mid - 1
            else: # 当找到其中一个目标值之后,分别向前和向后遍历,找到起始和终止位置
                flag = 1
                start = mid
                end = mid
                while start >= 0 and nums[start] == target:
                    start -= 1
                while end <= len(nums)-1 and nums[end] == target:
                    end += 1
                break
        if flag == 1:
            return [start+1,end-1]
        else:
            return [-1,-1]

时间复杂度: O ( n ) O(n) O(n),数组中存在很多重复的 target 时,该方法效率很低。

思路2:扩展二分查找

1️⃣查找左边界

  • 查找到任意一个target
  • 左边界 s t a r t start start必定在 [ l e f t , m i d − 1 ] [left,mid-1] [left,mid1]中,所以可以将 r i g h t = m i d − 1 right=mid-1 right=mid1,缩小区间,重新搜索一个新的target,新的target必定在源target的左侧
  • 因为想要最左侧target的索引,所以和LC704是一样的,最后返回的是 s t a r t = m i d start=mid start=mid

2️⃣查找右边界

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if nums is None or len(nums) == 0:
            return [-1,-1]
        # 扩展二分查找法:查找target时候使用二分查找法,确定边界的时候仍然使用二分查找法
        # 先用二分查找法找到左边界
        # 双闭区间(有重复元素)
        left = 0
        right = len(nums) - 1
        start = -1
        while left <= right:
            mid = left + (right - left) // 2
            if target > nums[mid]: # 应该删除前半部分的元素
                left = mid + 1
            elif target < nums[mid]: # 应该删除后半部分的元素
                right = mid - 1
            else: # 当找到其中一个目标值之后
                # 左边界start应该在[left,mid]之间
                right = mid - 1
                start = mid
        # 再用二分查找法找到右边界
        left = 0
        right = len(nums) - 1
        end = -1
        while left <= right:
            mid = left + (right - left) // 2
            if target > nums[mid]: # 应该删除前半部分的元素
                left = mid + 1
            elif target < nums[mid]: # 应该删除后半部分的元素
                right = mid - 1
            else: # 当找到其中一个目标值之后
                # 右边界end应该在[mid,right]之间
                left = mid + 1
                end = mid     
        return [start,end]   

时间复杂度: O ( l o g N ) O(logN) O(logN)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1539138.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣刷题31-33(力扣 0024/0070/0053)

今日题目&#xff1a; 24. 两两交换链表中的节点 题目&#xff1a;给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09; 思路&…

想来开视频号小店,你需要准备好这些内容!新手建议收藏!

大家好&#xff0c;我是电商小布。 视频号小店项目&#xff0c;作为当前在电商行业内推出时间最短的项目&#xff0c;内部发展潜力大&#xff0c;吸引到了很多小伙伴想要来入局其中。 而对于我们新手朋友来说&#xff0c;在开店的时候&#xff0c;不知道该准备哪些东西&#…

性能测试-Jmeter中IF控制器使用

一、Jmeter控制器 分为两种类型&#xff1a; 控制测试计划执行过程中节点的逻辑执行顺序&#xff0c;如&#xff1a;循环控制器&#xff0c;if控制器等对测试计划中的脚本进行分组&#xff0c;方便Jmeter统计执行结果以及进行脚本的运行时控制等&#xff0c;如&#xff1a;吞…

小红书扫码登录分析与python实现

文章目录 1. 写在前面2. 接口分析3. 代码实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

Unity发布webgl之后打开PDF文件,不使用js,不和浏览器交互

创建一个按钮&#xff0c;然后点击就会打开 在webgl下要使用这样的路径拼接&#xff0c;不然就会报错。 btnBook.onClick.AddListener(() >{var uri new System.Uri(Path.Combine(Application.streamingAssetsPath "/Books", "文档.pdf"));Debug.Log…

mysql80-DBA数据库学习1

掌握能力 核心技能 核心技能 mysql部署 官网地址www.mysql.com 或者www.oracle.com https://dev.mysql.com/downloads/repo/yum/ Install the RPM you downloaded for your system, for example: yum install mysql80-community-release-{platform}-{version-number}.noarch…

Python代码实现Excel表格转HTML文件

Excel工作簿是常用的表格格式&#xff0c;广泛用于组织、分析及展示数据。Excel文件通常需要专门的文档阅览器进行查看。如果我们想要以更兼容的方式展示Excel表格&#xff0c;可以将其转换为HTML格式&#xff0c;使其能够在各种浏览器中直接进行查看。同时&#xff0c;将Excel…

政安晨:【深度学习实践】【使用 TensorFlow 和 Keras 为结构化数据构建和训练神经网络】(六)—— 二元分类

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 这篇文章咱们将深度学习应用到另一个常见任务中。 前言 在…

基于Matlab的眼底图像血管分割,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

前端应用开发实验:条件渲染和循环渲染

目录 实验目的相关知识点实验内容图片的隐藏和显示代码实现效果 电影票房排序代码实现效果 代办事项记录代码实现效果 实验目的 (1)熟练掌握v-on 指令的用法&#xff0c;学会使用v-on 指令监听DOM元素的事件&#xff0c;并通过该事件触发调用事件处理程序。 (2)掌握v-on指令修…

FFmepg--AVFilter过滤器使用以及yuv视频裁剪

文章目录 AVFilter 流程:api核心代码变量yuv视频裁剪AVFilter 流程: ⾸先使⽤split滤波器将input流分成两路流(main和tmp),然后分别对两路流进⾏处理。对于tmp流,先经过crop滤波器进⾏裁剪处理,再经过flip滤波器进⾏垂直⽅向上的翻转操作,输出的结果命名为flip流。再将…

【技术栈】Redis 中的事务及持久化方式

SueWakeup 个人主页&#xff1a; SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;保留赤子之心也许是种幸运吧 本文封面由 凯楠&#x1f4f8; 友情提供 目录 相关传送门 1. Redis 中的事务 2. Redis 持久化 2.1 RDB 方式 2.1.1 RDB手动 2.1.2 RDB自动 2.…

百度地图的使用(js,vue2,vue3)

百度地图的使用 创建应用获取密钥ak ak 是在百度地图开发者平台申请的密钥 详见 http://lbsyun.baidu.com/apiconsole/key 一、原生js使用百度地图 引入<script type"text/javascript" src"https://api.map.baidu.com/api?v1.0&typewebgl&ak你的ak&…

后端系统开发之——功能完善

原文地址&#xff1a;https://blog.yiming1234.cn/?p830 下面是正文内容&#xff1a; 前言 通过SpringBoot开发用户模块的部分也就差不多要结束了&#xff0c;这一片文章就主要提一些在系统开发中需要注意到的细节部分和功能&#xff0c;也就是剩余的部分。 但是这个专栏只介…

一键掌控:Shell脚本自动化安装与管理Conda环境的艺术

前面写了个博客《conda&#xff1a;解决多项目开发环境配置的神器&#xff01;》简单介绍了 Conda 的安装和基本命令&#xff0c;在做开发时经常会使用 Conda 建立多个应用环境&#xff0c;Conda 的命令虽不复杂&#xff0c;但还是有时会弄混&#xff0c;所以就考虑写个脚本&am…

pyrealsense2获取保存点云

一、第一种实现代码 Python import sys import cv2 import pyrealsense2 as rs import numpy as np import keyboard import open3d as o3d import osif __name__ "__main__":output_folder output_data/os.makedirs(output_folder, exist_okTrue)pipeline rs.p…

Prometheus+Grafana 监控Tongweb嵌入式(by lqw)

文章目录 1.思路2.部署准备3.Grafana仪表盘json文件下载4.tw嵌入式jar包本地引入依赖并测试运行5.运行jmx_prometheus_javaagent-0.19.0.jar形式获取监控数据&#xff08;方法一&#xff09;6.使用Actuator 获取监听数据&#xff08;方法二&#xff09;7.Prometheus部署8.Prome…

项目配置之道:优化Scrapy参数提升爬虫效率

前言 在当今信息时代&#xff0c;数据是无处不在且无比重要的资源。为了获取有效数据&#xff0c;网络爬虫成为了一项至关重要的技术。Scrapy作为Python中最强大的网络爬虫框架之一&#xff0c;提供了丰富的功能和灵活的操作&#xff0c;让数据采集变得高效而简单。本文将以爬…

算法打卡day25|回溯法篇05|Leetcode 491.递增子序列、46.全排列、47.全排列 II

算法题 Leetcode 491.递增子序列 题目链接:491.递增子序列 大佬视频讲解&#xff1a;递增子序列视频讲解 个人思路 和昨天的子集2有点像&#xff0c;但昨天的题是通过排序&#xff0c;再加一个标记数组来达到去重的目的。 而本题求自增子序列&#xff0c;是不能对原数组进行…

Prometheus+Grafana 监控Tongweb7(by lqw)

文章目录 1.准备工作2.Tongweb7部署3.Prometheus部署4.上传jar包并配置Tongweb75.Prometheus配置6.安装和配置Grafana 1.准备工作 本次参考&#xff1a;Prometheus监控Tongweb容器 1.使用虚拟机ip&#xff1a;192.168.10.51&#xff08;tongweb&#xff09;&#xff0c;192.1…