【二分查找】【刷题笔记】——灵神题单1

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

灵神b站讲解

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

暴力做法:遍历所有元素,询问是否大于等于8

没有利用数组是有序的这个特性

二分法思路:

  • 设定左右指针,随便取个数字,与目标值对比
  • 取在中间最合适,如果有奇数个数字,取左边合适下取整,
  • 用红色表示小于8,蓝色表示大于8

闭区间写法

  • LR分别指向询问的左右边界,即闭区间[L, R]
  • M指向当前正在询问的数,
  • 如果M<target:在这里插入代码片
    • 标记[L, M]都为红色
    • 剩余不确定的区间是[M+1, R]
    • 因此下一步是令L等于M+1
    • 同时强调了闭区间属性,L-1一定是红色
  • 同上,如果M大于等于8,M标记为蓝色
    • M, R\]都是蓝色

    • 因此下一步令R等于M-1
    • 同时强调了,R+1一定指向了蓝色
  • 不断询问:
  • 关键点:循环不变量 :
    • L-1始终指向红色,
    • R+1始终指向蓝色,大于等于目标的数
  • 由于循环不变量,R+1就是答案
  • 由于循环结束后R+1=L
  • 答案也可用L表示
  • 如果所有元素都比target小,L不断向后移动,最后等于数组长度

闭区间写法

python 复制代码
    def SearchTarget(self, nums: List[int], target: int) -> List[int]:
        n = len(nums)
        left = 0
        right = n - 1        # 闭区间[left, right]
        
        while left <= right: # 区间不为空
            # 不溢出写法:left + (right - left) // 2
            mid = (left + right) // 2  # 下取整,取偏左边那个
            if nums[mid] < target:   # 需要询问的区间缩小到了,mid+1开始
                left = mid + 1      # [mid + 1, right]
            else:    # nums[mid] >= target
                right = mid - 1         # [left, mid-1]
        return left   # 或right + 1 = left片

左闭右开写法

python 复制代码
    def SearchTarget(self, nums: List[int], target: int) -> List[int]:
        n = len(nums)
        left = 0
        right = n       # 闭区间[left, right)
        
        while left < right: # 区间不为空,l=r就是空了对于左闭右开
            # 不溢出写法:left + (right - left) // 2
            mid = (left + right) // 2  # 下取整,取偏左边那个
            if nums[mid] < target:   # 需要询问的区间缩小到了,mid+1开始
                left = mid + 1      # [mid + 1, right]
            else:    # nums[mid] >= target
                right = mid          # [left, mid)
        return left   # 或right  = left片

开区间写法

python 复制代码
    def SearchTarget(self, nums: List[int], target: int) -> List[int]:
        n = len(nums)
        left = -1
        right = n       # 开区间(left, right)
        
        while left + 1 < right: # 区间不为空,l=r就是空了对于左闭右开
            # 不溢出写法:left + (right - left) // 2
            mid = (left + right) // 2  # 下取整,取偏左边那个
            if nums[mid] < target:   # 需要询问的区间缩小到了,mid+1开始
                left = mid + 1      # [mid + 1, right]
            else:    # nums[mid] >= target
                right = mid          # [left, mid)
        return right  #

回到本题,要求返回target的开始位置和结束位置。也就是分别是大于等于和小于等于

  • 原问题是找出有序数组中第一个大于等于8的数的位置,可以用上面的三种方法找到!
  • 假如现在问题是找到大于8的数的位置,可以转化为大于等于8+1的问题
  • 如果问题是找到小于x的位置,可以看成是找到大于等于x的那个数的左边的数,也就是(大于等于x)-1的位置
  • 如果问题是找到 <=x 的数字位置,实际上是找>x的左边的位置也就是(>x) - 1的位置
python 复制代码
def lower_bound1( nums: List[int], target: int) -> List[int]:
    n = len(nums)
    left = 0
    right = n - 1        # 闭区间[left, right]
       
    while left <= right: # 区间不为空
        # 不溢出写法:left + (right - left) // 2
        mid = (left + right) // 2  # 下取整,取偏左边那个
        if nums[mid] < target:
            left = mid + 1      # [mid + 1, right]
        else:    # nums[mid] >= target
            right = mid - 1         # [left, mid]
    return left   # 或right + 1 = left


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        # lower_bound1()找的是>=target的第一个位置
        # 本题要找的是左右边界
        # 左边界就是lower_bound1(nums, target),就是>=target的位置
        start = lower_bound1(nums, target)
        if start == len(nums):  # 如果start位置超出了数组范围,说明压根没找到一个target!
            return [-1, -1]
        # 右边界就是找到<=target的位置,如何转换?
        # 那就找>target这个值,左边的位置
        # 即 lower_bound1(nums, target+1 ) - 1
        end = lower_bound1(nums, target + 1) - 1
        return [start, end]
________________________________
解答错误
77 / 88 个通过的测试用例

官方题解
输入
nums =
[5,7,7,8,8,10]
target =
6

添加到测试用例
输出
[1,0]
预期结果
[-1,-1]               

查看用例,发现问题是nums中根本没有target,这种情况同样返回[-1, -1]

python 复制代码
def lower_bound1( nums: List[int], target: int) -> List[int]:
    n = len(nums)
    left = 0
    right = n - 1        # 闭区间[left, right]
       
    while left <= right: # 区间不为空
        # 不溢出写法:left + (right - left) // 2
        mid = (left + right) // 2  # 下取整,取偏左边那个
        if nums[mid] < target:
            left = mid + 1      # [mid + 1, right]
        else:    # nums[mid] >= target
            right = mid - 1         # [left, mid]
    return left   # 或right + 1 = left


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        # lower_bound1()找的是>=target的第一个位置
        # 本题要找的是左右边界
        # 左边界就是lower_bound1(nums, target),就是>=target的位置
        start = lower_bound1(nums, target)
        if start == len(nums) or not target in nums or nums[start] != target:  # 如果start位置超出了数组范围,说明压根没找到一个target!
            return [-1, -1]
        # 右边界就是找到<=target的位置,如何转换?
        # 那就找>target这个值,左边的位置
        # 即 lower_bound1(nums, target+1 ) - 1
        end = lower_bound1(nums, target + 1) - 1
        return [start, end]
过了   
            

35.搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O ( l o g n ) O(log n) O(logn) 的算法。

  • 如果这个target比数组最大值都大说明,不存在与这个数组中,应该插入最后一个位置,也就是返回n
  • 上面学的三种模板都是找>=target的第一个位置,但实际上target不一定存在于nums,怎么办?
  • 实际上无所谓,就用那段代码就能找到!

闭区间写法

python 复制代码
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = 0
        right = n - 1     # 闭区间写法[L, R]
        
        while left <= right:                    # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
            mid = left + (right - left) // 2   # 中间位置或偏左
            if nums[mid] < target:             # 闭区间,则mid已经不满足
                left = mid + 1                 # 直接更新L为mid+1, [mid+1, R]
            else:                              # mid >= target, mid可能满足target
                right = mid - 1                # 为了求左边界(也就是大于等于的第一个),更新right=mid-1 
        return left

左闭右开写法

python 复制代码
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = 0
        right = n      # 左闭右开写法[L, R)
        
        while left < right:                    # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
            mid = left + (right - left) // 2   # 中间位置或偏左
            if nums[mid] < target:             # 闭区间,则mid已经不满足
                left = mid + 1                 # 直接更新L为mid+1, [mid+1, R]
            else:                              # mid >= target, mid可能满足target
                right = mid                    # 为了求左边界(也就是大于等于的第一个),更新right=mid-1 
        return left

开区间写法

错误写法

python 复制代码
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = -1
        right = n      # 开区间写法(L, R),实际[L+1, R-1]
        
        while left < right:                    # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
            mid = left + (right - left) // 2   # 中间位置或偏左
            if nums[mid] < target:             # 闭区间,则mid已经不满足
                left = mid                     # 更新L为mid, [mid+1, R]
            else:                              # mid >= target, mid可能满足target
                right = mid                    # 为了求左边界(也就是大于等于的第一个),更新right=mid-1 
        return left
------------------------------------------------------------------------------------------------------------------------------------
死循环!
------------------------------------------------------------------------------------------------------------------
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = -1
        right = n      # 开区间写法(L, R),实际[L+1, R-1]
        
        while left + 1 < right:                    # L=R时,mid=L=R,继续循环;如果相等时不循环,可能漏掉这个解
            mid = left + (right - left) // 2   # 中间位置或偏左
            if nums[mid] < target:             # 闭区间,则mid已经不满足
                left = mid                     # 更新L为mid, [mid+1, R]
            else:                              # mid >= target, mid可能满足target
                right = mid                    # 为了求左边界(也就是大于等于的第一个),更新right=mid-1 
        return left+1
------------------------------------------------------------------------------------
通过!

704.二分查找

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

  • 上道题完美契合简单的二分查找,就算target不在nums也可以计算出应该插入的位置
  • 现在,这道题唯一不同只是如果target不在nums,不返回应该插入位置,而是返回-1。简单!
python 复制代码
def lower_bound(nums, target):
    n = len(nums)
    left = 0
    right = n-1               # 闭区间[left, right]
    while left <= right:      # 可能存在left==right这个区间,这个区间可能就是target!,如果不加"=",就会忽略这个结果   
        mid = (left + right) // 2
        if mid < target:      # mid已经不符合
            left = mid + 1    # 直接更新左边界为mid+1即可
        else:                 # 虽然可能mid可能等于right
            right = mid - 1   #  更新right为mid-1
    return left  # 或r-1?
            
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        i = lower_bound(nums, target)  # 找出目标值应该在的位置,不一定存在
        return i if nums[i] == target else -1
____________________________
执行出错
IndexError: list index out of range
                ~~~~^^^
    return i if nums[i] == target else -1
Line 16 in search (Solution.py)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ret = Solution().search(param_1, param_2)
Line 46 in _driver (Solution.py)
    _driver()
Line 61 in <module> (Solution.py)
  • i有可能是n啊!所以应该加一个逻辑,如果是n也返回-1

无语住了!!!!不是这个错因啊啊啊啊!已经第n次出现这个错误了!!!是nums[mid]target比较!不是midtarget比较啊~

好奇怪!!写 return i if i<len(nums) and nums[i] == target else -1就能过。

return i if and nums[i] == target and i<len(nums) else -1就out list range!!!

为什么!!!

python 复制代码
def lower_bound(nums, target):
    n = len(nums)
    left = 0
    right = n-1               # 闭区间[left, right]
    while left <= right:      # 可能存在left==right这个区间,这个区间可能就是target!,如果不加"=",就会忽略这个结果   
        mid = (left + right) // 2
        if nums[mid] < target:      # mid已经不符合
            left = mid + 1    # 直接更新左边界为mid+1即可
        else:                 # 虽然可能mid可能等于right
            right = mid - 1   #  更新right为mid-1
    return left  # 或r-1?
            
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        i = lower_bound(nums, target)  # 找出目标值应该在的位置,不一定存在
        return i if i<len(nums) and  nums[i] == target  else -1

好的,以下是简要抓重点说明出现 IndexError 错误的原因:

整体思路

代码想用 lower_bound 函数通过二分查找找到目标值 target 在有序数组 nums 中可能插入的位置 i,再在 search 函数里判断该位置的值是否为 target,若是则返回 i,否则返回 -1。

错误原因

  • search 函数的 return i if nums[i] == target else -1 这行中,对于测试用例 nums = [-1, 0, 3, 5, 9, 12]target = 13 时:
    • lower_bound 函数因为 13 大于数组 nums 中的最大值 12,会返回数组长度 6(即 i = 6),表示若插入 13 应在数组末尾下一个位置。
    • 但当在 search 函数里直接用 nums[i] 访问数组元素时,i = 6 超出了数组 nums 的有效下标范围(数组下标是 0len(nums) - 1),所以就抛出了 IndexError: list index out of range 错误。

关键就是没先确认 i 是否在数组有效下标范围内就去访问数组元素,导致访问越界出错。正确做法是像 return i if i < len(nums) and nums[i] == target else -1 这样,先判断 i 的有效性再访问数组元素。

相关推荐
大筒木老辈子5 小时前
Linux笔记---协议定制与序列化/反序列化
网络·笔记
草莓熊Lotso5 小时前
【C++】递归与迭代:两种编程范式的对比与实践
c语言·开发语言·c++·经验分享·笔记·其他
我爱挣钱我也要早睡!8 小时前
Java 复习笔记
java·开发语言·笔记
汇能感知13 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun13 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao13 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾14 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT14 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
ST.J15 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin15 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全