leetcode 第41题 缺失的第一个正数

题目

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

 

进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗?

 

示例 1:

输入:nums = [1,2,0]
输出:3
示例 2:

输入:nums = [3,4,-1,1]
输出:2
示例 3:

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

提示:

0 <= nums.length <= 300
-231 <= nums[i] <= 231 - 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/first-missing-positive

思路

        这个题的难点就在于要求时间复杂度为O(n),并且空间复杂度为常数级别。

        O(n)的时间复杂度也就是要求常数次的遍历数组,空间复杂度为常数级别确实是很难满足。

        一开始偷巧,根据提示的0<= nums.length <= 300,那么最终解的最小正整数一定在1-301之间,用一个长度为300的数组保留下已经出现过的1-300之间的数字,最后遍历找到没出现的最小正整数,这是常数级别的空间和时间。其实这里更灵活一些,数组长度可以用nums.length(更进一步,可以用Math.min(nums.length, 300))。代码如下

class Solution {
    public int firstMissingPositive(int[] nums) {
        boolean []showup = new boolean[301];
        for (int data : nums) {
            if (data > 0 && data <= 300) {
                showup[data] = true;
            }
        }
        for (int i = 1; i < showup.length; i++) {
            if (!showup[i]) {
               return i;
            }
        }
        return 301;
    }
}

        但偷巧的方法毕竟不是正道。看了官方解法,发现原来还有一层灵活应用。那就是可以直接在原来的数组基础上进行修改,这个思路是可以延续偷巧的思路的,只是不需要新建一个数组了,而是在原来的数组基础上进行修改。

        修改的方法比较容易想到的一个就是:用索引和数字一一对应(nums[i] = i + 1),如果当前遍历到的数字不满足这个关系,就将当前数字放到它该放的地方去,代码如下:

class Solution {
    public int firstMissingPositive(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            int data = nums[i];
            // 如果数字在[0,nums.length]之间,且data不在它应该在的位置,就进行交换
            if (data > 0 && data <= nums.length && data != (i + 1)) {
                 nums[i] = nums[data - 1];
                 nums[data - 1] = data;
                 if (data != nums[i]) {  // 这里是为了避免换回来的数字和原来一样,会死循环
                     i--;
                 }
            }
        }

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

        return nums.length + 1;
    }
}

        官方解法里还给了另一种思路,也是非常巧妙,不需要数字置换,而是通过给指定索引处加标记的方式。那么怎么加标记呢?首先把数组中所有不在[1,nums.length]之间的数组全部替换成nums.length+1,这样所有的数字就都是正数了。然后就可以通过给指定索引加负号的形式加标记。最后数组中所有带负号的位置i就代表原始数组中有 i+1这个数字。遍历数组,找到第一个不带负号的k,返回k+1即可。代码如下:

class Solution {
    public int firstMissingPositive(int[] nums) {
        // 先把所有不符合条件的数字都替换成nums.length+1
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] <= 0 || nums[i] > nums.length) {
                nums[i] = nums.length + 1;
            }
        }

        // 给符合条件的数字,在指定索引出加负号标记
        for (int data : nums) {
            data = Math.abs(data);  // 因为可能有符号标记,所以取绝对值
            if (data > 0 && data <= nums.length) {
                nums[data - 1] = -Math.abs(nums[data - 1]); // 因为可能之前被加过标记,需要取绝对值
            }
        }

        // 遍历找到第一个不带负号的位置
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) {
                return i + 1;
            }
        }

        return nums.length + 1;
    }
}

耗时 50分钟

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值