【力扣算法】41-缺失的第一个正数

题目

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

示例 1:

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

示例 2:

输入: [3,4,-1,1]
输出: 2

示例 3:

输入: [7,8,9,11,12]
输出: 1

说明:

你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。

题解

方法 1:索引作为哈希表。

数据预处理

首先我们可以不考虑负数和零,因为不需要考虑。同样可以不考虑大于 n 的数字, 因为首次缺失的正数一定小于或等于 n + 1 。 缺失的正数为 n + 1 的情况会单独考虑。

41_max_possible_first.png

为了不考虑这些数,又要保证时间复杂度为 O ( N ) \mathcal{O}(N) O(N) ,因此 不能将这些元素弹出。我们可以将这些数用 1 替换。

41_replace.png

为了确保缺失的第一个正数不是 1,先要在这步操作前确定 1 是否存在。

如何实现就地算法

现在我们有一个只包含正数的数组,范围为 1 到 n, 现在的问题是在 O ( N ) \mathcal{O}(N) O(N) 的时间和常数空间内找出首次缺失的正数。

如果可以使用哈希表,且哈希表的映射是 正数 -> 是否存在 的话,这其实很简单。

41_missing.png

“脏工作环境” 的解决方法是将一个字符串 hash_str 分配 n 个 0,并且用类似于哈希表的方法,如果在数组中出现数字 i 则将字符串中 hash_str[i] 修改为 1 。

41_string.png

我们不使用这种方法,但是借鉴这种 使用索引作为哈希键值 的想法。

最终的想法是 使用索引作为哈希键 以及 元素的符号作为哈希值 来实现是否存在的检测。

例如,nums[2] 元素的负号意味着数字 2 出现在 nums 中。nums[3]元素的正号表示 3 没有出现在 nums 中。

为了完成此操作,我们遍历一遍数组(该操作在数据预处理使得数组中只有正数的操作后),检查每个元素值 elem 以及将nums[elem] 元素的符号变为符号来表示数字 elem 出现在 nums 中。注意,当数字出现多次时需要保证符号只会变化 1 次。

算法

现在可以开始写算法了。

  • 检查 1 是否存在于数组中。如果没有,则已经完成,1 即为答案。
  • 如果 nums = [1],答案即为 2 。
  • 将负数,零,和大于 n 的数替换为 1 。
  • 遍历数组。当读到数字 a 时,替换第 a 个元素的符号。 注意重复元素:只能改变一次符号。由于没有下标 n ,使用下标 0 的元素保存是否存在数字 n。
  • 再次遍历数组。返回第一个正数元素的下标。
  • 如果 nums[0] > 0,则返回 n 。
  • 如果之前的步骤中没有发现 nums 中有正数元素,则返回n + 1。

代码

class Solution {
  public int firstMissingPositive(int[] nums) {
    int n = nums.length;

    // 基本情况
    int contains = 0;
    for (int i = 0; i < n; i++)
      if (nums[i] == 1) {
        contains++;
        break;
      }
    
    if (contains == 0)
      return 1;
    
    // nums = [1]
    if (n == 1)
      return 2;
    
    // 用 1 替换负数,0,和大于 n 的数
    // 在转换以后,nums 只会包含正数
    for (int i = 0; i < n; i++)
      if ((nums[i] <= 0) || (nums[i] > n))
        nums[i] = 1;
    
    // 使用索引和数字符号作为检查器
    // 例如,如果 nums[1] 是负数表示在数组中出现了数字 `1`
    // 如果 nums[2] 是正数 表示数字 2 没有出现
    for (int i = 0; i < n; i++) {
      int a = Math.abs(nums[i]);
      // 如果发现了一个数字 a - 改变第 a 个元素的符号
      // 注意重复元素只需操作一次
      if (a == n)
        nums[0] = - Math.abs(nums[0]);
      else
        nums[a] = - Math.abs(nums[a]);
    }
    
    // 现在第一个正数的下标
    // 就是第一个缺失的数
    for (int i = 1; i < n; i++) {
      if (nums[i] > 0)
        return i;
    }
    
    if (nums[0] > 0)
      return n;
    
    return n + 1;
  }
}

复杂性分析

时间复杂度: O ( N ) O(N) O(N) 由于所有的操作一共只会遍历长度为 N 的数组 4 次。
空间复杂度: O ( 1 ) O(1) O(1) 由于只使用了常数的空间。
作者:LeetCode
链接:https://leetcode-cn.com/problems/two-sum/solution/que-shi-de-di-yi-ge-zheng-shu-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

感想

题解还是牛啊,自己还是只会HashSet弄一弄。不知道HashSet算不算做到题目要求的就地解决,道理上来说不扩容就也是O(1)空间复杂度,扩容的时候感觉就不算O(1)了(所以应该是没有完成题目的要求,捂脸):

执行用时 : 5 ms, 在First Missing Positive的Java提交中击败了46.01% 的用户

内存消耗 : 34.5 MB, 在First Missing Positive的Java提交中击败了88.78% 的用户

class Solution {
    public int firstMissingPositive(int[] nums) {
        HashSet<Integer> hashSet = new HashSet<>();
        for(int i=0;i<nums.length;i++){
            if(nums[i]>0&&nums[i]<=nums.length) hashSet.add(nums[i]);
            //这里if判断不判断对效率影响不大,删除后依旧:
            //执行用时 : 5 ms, 在First Missing Positive的Java提交中击败了46.01% 的用户
            //内存消耗 : 34.2 MB, 在First Missing Positive的Java提交中击败了92.88% 的用户
        }
        for(int i=1;i<=nums.length;i++){
            if(!hashSet.contains(i)) return i;
        }
        return nums.length+1;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值