• 剑指 Offer 53 - II. 0~n-1中缺失的数字


    1:问题描述

    来源:LeetCode

    难度:简单


    问题详情:
    一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

    示例 1:

    输入: [0,1,3]
    输出: 2
    
    • 1
    • 2

    示例 2:

    输入: [0,1,2,3,4,5,6,7,9]
    输出: 8
    
    • 1
    • 2

    2:问题分析

    2.1 时间复杂度和空间复杂度

    在真正开始介绍各种算法前,先以表格形式展示各自的时间复杂度和空间复杂度,其中 n n n表示数组nums的长度。

    算法时间复杂度空间复杂度
    暴力查询 O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
    二分查找O( l o g ( n ) log(n) log(n)) O ( 1 ) O(1) O(1)

    2.2 暴力查询

    暴力查询的思想是遍历数组的每一个元素,并判断当前元素值是否与当前下标值是否相等,如果不相等,就说明当前下标值缺失,返回即可。
    举例来说:

    nums=[0,1,3]
    
    • 1

    在遍历到nums[2]时发现其并不是等于2,所以可以断定2是缺失的数据、

    特例说明,对于nums=[0,1,2]这样看着并不缺失数据的数组,因为题目中明确说明缺失了一位,那么只能是3是缺失的数值。

    2.2.1 代码

    def missingNumber(self, nums: List[int]) -> int:
        for i, item in enumerate(nums):
            if i != item:
                return i
        return i+1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

    2.3 二分查找

    题目中说了nums是个有序数组,对于有序数组的搜索问题,那么自然就能想到使用二分查找的方法。

    2.3.1 二分查找1

    一开始本人想到的是利用左右指针指向的数据之和与“两位中位数”之和的大小来判断到底是哪个区间缺失了。
    举例来说:

    nums=[0,1,3,4]
    
    • 1
    1. l e f t = 0 , r i g h t = 3 , m i d 1 = ( l e f t + r i g h t ) / / 2 = 1 , m i d 2 = ( l e f t + r i g h t − 1 ) / / 2 = 1 left = 0, right=3, mid1 = (left + right)//2=1, mid2 = (left+right-1)//2=1 left=0,right=3,mid1=(left+right)//2=1,mid2=(left+right1)//2=1
    2. n u m s [ 0 ] + n u m s [ 3 ] = 4 , n u m s [ 1 ] + n u m s [ 1 ] = 3 , 4 < 3 nums[0]+nums[3]=4,nums[1]+nums[1]=3,4<3 nums[0]+nums[3]=4,nums[1]+nums[1]=3,4<3,而且可以看出是中位数的右区间缺失,说明右边的数据缺失, n u m s [ l e f t ] + n u m s [ r i g h t ] < n u m s [ m i d 1 ] + n u m s [ m i d 2 ] nums[left]+nums[right]nums[left]+nums[right]<nums[mid1]+nums[mid2]

    但是这种想法实际实现出来,需要考虑的特殊情况太多,暂时不做过多介绍,贴出代码:

    def missingNumber(self, nums: List[int]) -> int:
        length = len(nums)
        # 特殊处理[1,2]这类缺失0的数据
        if nums[0] != 0:
            return 0
        if length == 1:
            return length
        left, right = 0, length - 1
        missing = 'left'  # 表示中位数的左边还是右边缺失
        while left < right:
            t1 = nums[left] + nums[right]
            length = right - left + 1
            a = left + length // 2
            b = left + (length - 1) // 2
            t2 = nums[a] + nums[b]
            if t1 == t2:
                if nums[a] - nums[b] == 2:
                    return nums[a] - 1
                if missing == 'right':
                    return nums[left] - 1
                else:
                    return nums[right] + 1
            # 比如0 1 2 3 4 5 6 7 中位数是3 和 4,如果缺失 1的话,那就是0 2 3 4 5 6 7,t1 = 7, t2 = 8
            # 也就是缺失中位数左边的数值,t1 < t2
            elif t1 < t2:
                missing = 'left'
                right = b
    
            else:
                missing = 'right'
                left = a
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.3.1 二分查找2

    但是有另一种基于二分查找思想的解法。
    举例来说:

    nums=[0,1,3,4]
    
    • 1
    1. l e f t = 0 , r i g h t = 3 left =0,right=3 left=0,right=3
    2. m i d = ( l e f t + r i g h t ) / / 2 = 1 mid = (left + right)//2=1 mid=(left+right)//2=1
    3. 如果 n u m s [ m i d ] = m i d nums[mid] =mid nums[mid]=mid,那么说明 [ l e f t , m i d ] [left,mid] [left,mid]区间内的数据是没有缺失的(这里可以看出来是与暴力查询的思想是相同的), l e f t = m i d + 1 left=mid+1 left=mid+1
    4. 如果不相等,那么说明 [ l e f t , m i d ] [left,mid] [left,mid]区间内的数据是缺失的, r i g h t = m i d − 1 right=mid-1 right=mid1

    代码如下:

    def missingNumber(nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        # 这里取=是为了判断left=right时,当前元素的数值是否等于索引
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == mid:
                left = mid + 1
            else:
                right = mid - 1
        return left
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    时间复杂度为 O ( l o g ( n ) ) O(log(n)) O(log(n)),空间复杂度为 O ( 1 ) O(1) O(1)

  • 相关阅读:
    SpringBoot隐藏文件
    玩转技巧|如何安全和方便地操作 Github
    MySQL 日志管理
    JS进阶第一篇:手写call apply bind
    基于Echarts实现可视化数据大屏大数据可视化
    【yolov4】基于yolov4深度学习网络目标检测MATLAB仿真
    【相机标定&基于消失点的外参标定】
    《向量数据库指南》——向量数据库内核面临的技术挑战及应对措施
    【HDLBits 刷题】Circuits(1)Combinational Logic
    【C++】内联函数、auto、范围for
  • 原文地址:https://blog.csdn.net/weixin_43490422/article/details/126802576