【二分查找】【刷题笔记】——灵神题单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]都是蓝色
    • 剩余不确定区间是[L, M-1]
    • 因此下一步令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 的有效性再访问数组元素。

相关推荐
西洼工作室1 小时前
【java 正则表达式 笔记】
java·笔记·正则表达式
初学者7.1 小时前
Webpack学习笔记(2)
笔记·学习·webpack
新手上路狂踩坑2 小时前
Android Studio的笔记--BusyBox相关
android·linux·笔记·android studio·busybox
stm 学习ing4 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl
尘觉4 小时前
算法的学习笔记—扑克牌顺子(牛客JZ61)
数据结构·笔记·学习·算法
bohu834 小时前
sentinel学习笔记1-为什么需要服务降级
笔记·学习·sentinel·滑动窗口
初学者7.5 小时前
Webpack学习笔记(3)
笔记·学习·webpack
bohu836 小时前
sentinel学习笔记5-资源指标数据统计
笔记·sentinel·statisticslot
璞~7 小时前
MQTT 课程概览 (学习笔记)02
笔记·学习
小王爱吃月亮糖7 小时前
C++进阶-1-单继承、多继承、虚继承
开发语言·c++·笔记·学习·visual studio