53 - II. 0~n-1中缺失的数字

本文介绍了解决剑指Offer53-II题目的两种方法:下标对应法和二分法。下标对应法通过遍历数组比较索引与元素值来找出缺失数字,时间复杂度为O(n)。二分法则利用数组递增特性,通过二分查找不满足条件的左边界,时间复杂度为O(logn)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

示例 1:

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

示例 2:

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

限制:

  • 1 <= 数组长度 <= 10000

解法一:下标对应法

数组长度n-1,元素范围0 ~ n-1,共n个数,如果数组长度是n的话,也即所有数都出现一次,那么排序后,数组中当前元素的值应该和其所在索引相同,我们找到第一个不同的,下标代表的就是缺失的数。

要注意的是当所有的nums[i] = i时,缺失的就是最后一个n-1,即nums.length

时间复杂度: O ( n ) O(n) O(n)
在这里插入图片描述

Java代码

class Solution {
    public int missingNumber(int[] nums) {
        for(int i = 0;i < nums.length;i++){
            if(nums[i] != i){
                return i;
            }
        }
        return nums.length;
    }
}

在这里插入图片描述

解法二:二分

这道题目给定的是递增数组,假设数组中第一个缺失的数是 x,那么数组中的数如下所示;
在这里插入图片描述
从中可以看出,数组左边蓝色部分都满足nums[i] == i,数组右边橙色部分都不满足nums[i] == i,满足了二分法中的二段性质,因此我们可以二分找出不满足nums[i] = i的左边界。

另外同解法一,要注意特殊情况:当所有数都满足nums[i] == i时,表示缺失的是 n

时间复杂度: 二分中的迭代只会执行 O ( l o g n ) O(logn) O(logn)次,因此时间复杂度是 O ( l o g n ) O(logn) O(logn)

Java代码

class Solution {
    public int missingNumber(int[] nums) {
        //二分,找到不满足nums[i] == i的左边界
        //要注意的是当所有的nums[i]=i时,缺失的就是最后一个n-1,即nums.length
        int l = 0,r = nums.length - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(nums[mid] != mid ) r = mid;
            else l = mid + 1; 
        }
        
        if(nums[r] == r) return nums.length;
        else return r;
    }
}

在这里插入图片描述

扩展题:LeetCode 268. 丢失的数字

268. 丢失的数字

给定一个包含 [0, n] n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

示例 1:

输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2是丢失的数字,因为它没有出现在 nums 中。

示例 2:

输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2是丢失的数字,因为它没有出现在 nums 中。

示例 3:

输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围[0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

输入:nums = [0] 输出:1 解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

提示:

  • n == nums.length
  • 1 <= n <= 10^4
  • 0 <= nums[i] <= n
  • nums 中的所有数字都 独一无二

进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?

解题思路

和上题几乎完全一样,不同点在于该题给的数组不是排序好的 。

Go代码

func missingNumber(nums []int) int {
    // 0~n一共有n+1个数,而nums只含n个,所以一定存在一个数缺失了
    // 定义一个n+1的数组,我们将数字都放到对应的索引处,然后再遍历一次数组
    // 哪个索引处的元素,索引和元素数值不同,就是缺失的数字
    arr := make([]int,len(nums) + 1)
    // 由于默认值就是0,如果缺失的是0,也会符合索引位置和对应的值相等,所以想让索引0位置赋值为-1
    arr[0] = -1
    for i := 0;i < len(nums);i++ {
        arr[nums[i]] = nums[i]
    }
    for i := 0;i < len(arr);i++ {
        if i != arr[i] {
            return i
        }
    }
    return -1
}

在这里插入图片描述

扩展题 LeetCode 41. 缺失的第一个正数

41. 缺失的第一个正数

给你一个未排序的整数数组nums,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。

示例 2:

输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

示例 3:

输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。

提示:

  • 1 <= nums.length <= 10^5
  • -2^31 <= nums[i] <= 2^31 - 1

解题思路

与上两题差不多,直接看代码注释。

Java代码

class Solution {
    public int firstMissingPositive(int[] nums) {
        /*
        思路:数组最多存放nums.length个正数,我们可以把在1~nums.length范围内的数放到相应的位置
        最后遍历一遍数组,如果nums[i] <=0 || nums[i] > nums.length  || nums[nums[i]-1]!=nums[i],则i+1就是缺失的第一个正数
        也即nums[i]如果在[1,nums.length]范围内,则应该放到i+1的位置,即i+1 = nums[i];也即nums[nums[i]-1]=nums[i]
        比如【1,2,3,5】==》i=3的位置本应该放i+1=4的,但现在放的5,说明4是缺失的第一个正数
        【1,3,4,2】==》【1,2,3,4】每个位置放的都是i+1,所以缺失的第一个正数是5,即nums.length+1
        【3,4,-1,1】==》【1,-1,3,4】 i=1的位置上本应该放i+1=2的,但现在放的-1,故2就是缺失的第一个正数
        */
        
        if(nums==null || nums.length==0) return 1;
        for(int i = 0;i<nums.length;i++){
            //如果nums[i]在[1,nums.length]范围内,却没有放在nums[i]-1的位置,那么就交换这两个位置上的数,
            //交换后,交换得来的num[i]可能也没有在正确位置,所以是while循环,直到nums[i]不再满足while条件
            //这样外层for循环结束后,每个在[1,nums.length]范围内的nums[i]都放在了正确的位置
            //注:nums[nums[i]-1]!=nums[i]对于【1,1】这样的数组也适用,因为该条件考虑的是nums[i]要放的位置是否已经放着正确的数
            //但这里如果写成i!=nums[i]-1,则会出现死循环,因为如果原本需要交换的位置已经放着正确的数,即数值也是nums[i],那么又需要交换,交换,一直是他们两个互相交换,while无法退出了
            while(nums[i] >0 && nums[i] <= nums.length  && nums[nums[i]-1]!=nums[i]){
                int temp = nums[nums[i]-1];
                nums[nums[i]-1] = nums[i];
                nums[i] = temp;//例如[1,3,4,-1,7],i为1时,3应该放在下标为2的位置=》【1,4,3,-1,7】,现在换回4,4,应该放在下标为3的位置=》【1,-1,3,4,7】
                //现在nums[1] = -1,不满足while的条件了,退出while,进行下一轮for,即i=2;
            }
        }
        
        for(int i = 0;i<nums.length;i++){
            //因为在[1,nums.length]范围内的数,都已经被我们排在了i+1的位置了
            //所以遍历所有项,如果nums[i]不等于i+1,说明i+1就是缺失的第一个数
            if(nums[i]!=i+1){
                return i+1;
            }
        }
        return nums.length+1;
    }
}

在这里插入图片描述

Go代码

func firstMissingPositive(nums []int) int {
    // 数组长度为len(nums),极端情况也就是他保存的是1~len(nums)个正数,那么缺失的最小正数是len(nums) + 1
    // 所以我们将在1~len(nums)范围内的元素都放到对应的索引-1处(因为索引从0开始的),后续再看下哪个位置的值不等于索引+1
    // 该索引处的就是缺失的最小正数
    for i := 0;i < len(nums);i++ {
        // 在范围内的放到对应的位置去
        for  nums[i] > 0 && nums[i] <= len(nums) && nums[nums[i] - 1] != nums[i] {
            nums[nums[i] - 1],nums[i] = nums[i],nums[nums[i] - 1]
        } 
    }

    for i:=0;i < len(nums);i++ {
        if nums[i] != i + 1{
            return i+1
        }
    }

    // 前面的for没有return,说明数组被对应的正数填满了,缺失的是len(nums) + 1
    return len(nums) + 1
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值