LeetCode-41.缺失的第一个正数 原地哈希

这里是题目描述:LeetCode-41.缺失的第一个正数

本题的难度标注为hard。本题的难点在于解法需要同时满足 O(n) 的时间复杂度和常数级别的空间复杂度。

首先假设我们不去考虑空间复杂度的要求,那么如何在 O(n) 的时间开销下解决本题?对了,就是哈希表,像这种考察数组中数字是否存在、数字是否重复、数字重复次数的题,都可以考虑借助哈希表这种数据结构解题

借助哈希表的解法(不满足空间限制)

首先要知道一个前提,假设给定的数组nums的长度为n,那么数组缺失的第一个正数就一定在1~n+1这个范围内。我们建立一个哈希表,遍历数组nums,将数组中所有的数字都存入哈希表;然后再遍历数字1~n+1,第一个不存在于哈希表中的数字,就是nums中缺失的第一个正数,将该数字返回

本算法的时间复杂度为O(n),空间复杂度为O(n),不满足题干中的空间复杂度限制。

原地哈希解法

因为需要建立并维护一个哈希表,因此会超出时间复杂度,我们可以使用题干所给的数组nums来实现 “原地哈希” ,以满足空间复杂度限制。哈希表的本质其实也是数组,可以不另外建立哈希表而使用数组nums来实现哈希表的功能。基于“原地哈希”的解法参考了LeetCode官网的题解:原地哈希解法

同样适用“原地哈希”来解的题还有:LeetCode-442.数组中重复的数据LeetCode-448.找到所有数组中消失的数字

下面是原地哈希解法的过程:

  1. 处理特殊情况
    1.1 特殊情况1,数组nums长度为0。直接返回结果1
    1.2 特殊情况2,数组nums长度为1。若nums[0]==1,直接返回结果2;若nums!=1,直接返回结果1
  2. 若不是nums长度为01的特殊情况,则开始原地哈希。这里原地哈希的准则是:nums1~n的数字分别放在nums0~n-1的下标上,也就是说,把数组nums本身作为一个哈希表,哈希函数为f(nums[i])=nums[i]-1。对数组nums进行第一次遍历,通过原地哈希函数将遍历到的数字放入对应位置
    1.1 若遍历到的数字小于等于0或大于n,则该数字超范围,跳过该数字
    1.2 若遍历到的数字nums[i]满足0<nums[i]<=n则该数字没有超范围,将它和nums[nums[i]-1]交换,它就放到了原地哈希表对应的位置;特别的,如果nums[nums[i]-1]==nums[i],则表示遇到了重复数,跳过该数字
  3. 第二次遍历数组nums,如果发现下标i处的数字不是i+1,则i+1是确实的第一个正数,返回i+1。如果遍历完数组所有的下标i处的数字都是i+1,则缺失的第一个数字是n+1,返回n+1
    在这里插入图片描述
    在这里插入图片描述

原地哈希法题解代码:

class Solution {
    //原地哈希法。
    /* 设数组nums的长度为n,那么第一个缺失的正数肯定在1~n+1范围内
    * 将数组nums本身作为一个哈希表,将nums中1~n的数字分别放在nums的0~n-1的下标上
    * 最后检查nums,当有下标m没有被放入对应数字m+1,则m+1就是第一个缺失的数字
    * 如果所有下标都放入了对应数字,则第一个缺失的数字是n+1 */
    public int firstMissingPositive(int[] nums) {
        if(nums.length==0) //特殊情况1
        {
            return 1;
        }
        if(nums.length==1) //特殊情况2
        {
            if(nums[0]==1)
            {
                return 2;
            }
            else
            {
                return 1;
            }
        }
        int n=nums.length;
        int index=0;
        while(index<n)
        {
            int val=nums[index];
            if(val==index+1 || val<=0 || val>n) //当前位置已经放入了对应数字或该数字超范围,跳过
            {
                index++;
            }
            else //数字在1~n+1范围且没有放在对应位置
            {
                if(nums[val-1]==val) //遇见了重复的数字
                {
                    index++;
                    continue;
                }
                nums[index]=nums[val-1];
                nums[val-1]=val;
            }
        }
        for(int i=0;i<n;i++) //第二次遍历,检查没有被放入对应数字的下标位置
        {
            if(nums[i]!=i+1)
            {
                return i+1;
            }
        }
        return n+1;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值