Loading

LeetCode41——缺失的第一个正数

题目描述


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

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

要求:时间复杂度为 \(O(N)\),只能使用常数个额外空间。

题解


抛去时间复杂度和空间复杂度要求的话,最容易想到的方法是先对数组进行排序,然后挨个遍历就能找到没有出现的最小正整数,这样的时间复杂度为 \(O(NlogN + N)\)
但是本题对时间复杂度和空间复杂度做了要求。假设先不考虑空间复杂度,想要将 \(O(NlogN + N)\) 的时间复杂度降为 \(O(N)\),就意味着不能对原数组进行常规的排序处理,这时就可以想到使用类似于计数排序的方法来降低时间复杂度,也可以使用 HashSet 来辅助,这样就能将时间复杂度成功降到 \(O(N)\)。再来考虑如何降低线性空间复杂度到常数级别,无论对于计数排序还是使用HashSet来说,其实都应用了哈希表的思想,用到哈希表,那就意味着必然要使用到额外的线性空间,想要将线性的额外空间降低到常数级别,也就意味着需要我们自己去编写哈希规则,或者说,实现 原地哈希

回归到题中,要找数组中未出现的最小正整数。假设数组长度为 \(N\),要找的目标值 \(target\) 一定在 \([1,N+1]\) 中,证明这一点的方法也很简单,假设目标值不在 \([1,N+1]\) 中,首先,数组所有元素就必然互不相等且在 \([1,N+1]\) 之间,这样 \(target\) 也必然会在 \([1,N+1]\) 中,推出矛盾,说明目标值一定在 \([1,N+1]\) 区间里。

那么按照这样的思路:将 \(1\) 这个数字放到数组索引为 \(0\) 的位置上,\(2\) 放到数组索引为 \(1\) 的位置上,以此类推,将所有在 \([1,N+1]\) 区间内的元素放到相应的位置,然后再遍历一遍数组,第一个值不等于数组索引位置 + 1 的元素即为要找的目标值。

代码如下:

class Solution {
    public int firstMissingPositive(int[] nums) {
        if(nums.length == 0){
            return 1;
        }
        for(int i = 0; i < nums.length;i++){
            while(nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] != nums[i] ){
                //注意这里的nums[nums[i]-1] != nums[i] 不可以写成 nums[i] != i + 1 
                //因为nums[nums[i]-1] != nums[i]表示数组里下标为 nums[i] - 1 的数值不等于下标为 i 的数值。
                //更通俗点说,就是判断当前元素是否已经被放到了正确位置,没有则交换,若已经放到了正确位置,则不进行交换
                //而 nums[i] != i + 1 根本无法判断当前元素是否已经通过交换放到了正确位置
                //举个反例[1,1],若用 nums[i] != i + 1 做判断条件会死循环 
                swap(nums,nums[i] - 1,i);
            }
        }

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

    public void swap(int[] nums,int i,int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

代码中虽然使用了两层循环嵌套,但是其实内层循环中,每次交换都会有一个数字回归到相应位置,因此最坏情况下时间复杂度也只是 \(O(N)\)

posted @ 2020-11-27 11:18  Icdd  阅读(101)  评论(0)    收藏  举报