LeetCode高频题41. 缺失的第一个正数

LeetCode高频题41. 缺失的第一个正数

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述


文章目录

  • LeetCode高频题41. 缺失的第一个正数
    • @[TOC](文章目录)
  • 题目
  • 一、审题
  • 二、解题
  • 之前学过一个类似的题目,arr任意位置选择,求和sum,sum无法组合出来的第一个最小的数是多少?
  • 本题,还不是求和,而是就找第一个没有出现的那个数,最小的正数,缺它
  • 还有一种牛逼的排挤方法,看看L位置的数是不是我需要的,不需要放垃圾区,R--,1--R是我预期要出现的连续数字
  • 总结

题目

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

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


一、审题

示例 1:

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

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

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

提示:

1 <= nums.length <= 5 * 105
-231 <= nums[i] <= 231 - 1

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/first-missing-positive
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


二、解题

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

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

这是关键,否则你就需要排序o(nlog(n))
然后你再去遍历o(n)查找

这是不行的

你得一遍过,然后找到那个第一个没有出现的正数,就是缺谁

之前学过一个类似的题目,arr任意位置选择,求和sum,sum无法组合出来的第一个最小的数是多少?

这里有一个很重要的知识点:
目前0–i-1范围上能组合出最远的的最右右边界range
i位置来了一个[i],然后你就能表示一个新的最远右边界range+=[i]
还没有出现的最小那个正数就range+1;

比如下图,range目前是1,来了一个i是2,那最远可以求和表示到3,不能表示的就是4
依次类推
LeetCode高频题41. 缺失的第一个正数_第1张图片

本题,还不是求和,而是就找第一个没有出现的那个数,最小的正数,缺它

还不一样,所以要重新思考

[7,8,9,11,12]

可能是要用排挤的方式,将i下标放i+1值,这样的话,才能表示正数1—N都放好了
啥时候一直i一直没有i+1,说明i+1就是那个没出现的最小正数

LeetCode高频题41. 缺失的第一个正数_第2张图片
定义0–L-1范围中每一个位置i都放好了i+1,说明前面都是合格的
啥时候L那没法放i+1,就是i+1没出现

比如:arr=120
发现0和1下标i,都放了i+1,2那个位置放了个0,说明2+1没出现
LeetCode高频题41. 缺失的第一个正数_第3张图片

[7,8,9,11,12]
再看这个例子,当7已经超过了arr的下标返回,咱们,把它放到最右边就行了,说明左边一定有小的数没有出现。
我们看L=7这个地方
7-1超过4这个小标了,只能交换到最后位置R
R–
然后12-1超过4这个小标了,只能交换到R
R–
然后11-1超过4这个小标了,只能交换到R
R–
LeetCode高频题41. 缺失的第一个正数_第4张图片
然后9-1超过4这个小标了,只能交换到R
R–
发现L=0=R了,看看L处是否达标?因为8不是0+1,所以L也不达标
此时L+1=1就是第一个没出现的正数

反正,
(1)如果[i]可以取[i]-1位置,就去,这样L++,说明0–L-1范围内都是达标的(i处放i+1)
(2)如果,如果[i]不可以取[i]-1位置,就得放到[i] - 1那个位置,或者[i] - 1越界就得把[i]放到此时的R,交换,
当然你还需要查[i] - 1位置人家原本是否已经达标了,即[i] - 1放了[i]了
当然了,我们要换的是[i]>0的情况,那些负数,0,绕过了就
(3)直到L=R碰到,最后看看L处是否达标,不是,L+1就是最小的那个没有出现的正数

这就是一个比较巧妙的排挤方法
手撕代码问题不大的,逻辑最重要了

        //复习:
        //定义0--L-1范围中每一个位置i都放好了i+1,说明前面都是合格的
        public int firstMissingPositiveReview(int[] nums) {
            int L = 0;
            int N = nums.length;
            int R = N - 1;
            while (L <= R){
                //如果L处放了L+1,就跳过,合法不管
                if (nums[L] == L + 1) L++;//说明0--L-1范围内都是达标的(i处放i+1)
                //如果,如果[i]不可以取[i]-1位置,就得放到[i] - 1那个位置,或者[i] - 1越界就得把[i]放到此时的R,交换,
                //当然你还需要查[i] - 1位置人家原本是否已经达标了,即[i] - 1放了[i]了
                //同时我们只处理大于0的数
                else if (nums[L] > 0 && nums[L] - 1 < N && nums[nums[L] - 1] != nums[L])
                    swap(nums, L, nums[L] - 1);
                //如果上面没有满足,那就要把[i]放到R处
                else swap(nums, L, R--);//然后R--
            }
            //L=R碰到,最后看看L处是否达标,不是,L+1就是最小的那个没有出现的正数

            return L + 1;//
        }

测试一把:

    public static void test(){
        Solution solution = new Solution();

        int[] arr = {7,8,9,11,12};
        System.out.println(solution.firstMissingPositive(arr));
        System.out.println(solution.firstMissingPositiveReview(arr));

        int[] arr2 = {1,2,0};
        System.out.println();
        System.out.println(solution.firstMissingPositive(arr2));
        System.out.println(solution.firstMissingPositiveReview(arr2));

        int[] arr3 = {3,2,-1,4};
        System.out.println();
        System.out.println(solution.firstMissingPositive(arr3));
        System.out.println(solution.firstMissingPositiveReview(arr3));

        int[] arr4= {1};
        System.out.println();
        System.out.println(solution.firstMissingPositive(arr4));
        System.out.println(solution.firstMissingPositiveReview(arr4));
    }


    public static void main(String[] args) {
        test();
    }
1
1

3
3

1
1

2
2

这个排挤的方法真的非常巧!
LeetCode测试:
LeetCode高频题41. 缺失的第一个正数_第5张图片
绝对的牛

还有一种牛逼的排挤方法,看看L位置的数是不是我需要的,不需要放垃圾区,R–,1–R是我预期要出现的连续数字

这么搞:
L含义不变,0–L-1上,i位置放i+1
(1)只要你来到L处,数字放的是L+1,L++,不管,继续

注意:R有含义了哦!!!最开始R=N,代表我非常期待1–N连续数字出现
LeetCode高频题41. 缺失的第一个正数_第6张图片

对吧
arr = 1 2 0,N=3,我期待1–3连续数字出现R=3就是这个意思

如果你中途发现有些数字压根不会让我的预期满足,就要跟R交换,让R–1
为啥呢,我希望连续出现1–R,

(2)但是中间你要是重复出现俩数,那就是不需要的
长度N就只能放N个数,你多了重复的数出现,就占据了多余空间,让R预期降低了,R–

2 2 1,俩2,最开始R=3,一旦碰到2个2,说明R=2,你最多只能得到1 2这种连续数字,不可能有3出现,重复的2占据了3的空间

对吧?

这种重复怎么表示呢?
就是[L]本来应该去[L]-1位置,但是你发现[L]-1位置竟然已经是[L]了
那多出来了[L]
LeetCode高频题41. 缺失的第一个正数_第7张图片
L=3,但是[3]=6你应该去6-1位置,但是5那已经放了[L]=6了
6重复出现了,必然R预期应该降低

(3)还有,万一你出现0和负数,也是我们不要的,也不能出现<=L的数,因为我期待L处放L+1,L左边已经好了的
由于L的含义是0–L-1就是达标的,0–L-1都让i放好了i+1值,所有你要是L处出现<=L的值,也是不行的

比如:前面有了123了,现在L=3,你L处绝对不能出现负数或者0,也不能出现<=L的数,因为我希望L放L+1,来了个左边的数,就是重复
因此让L和R那交换,进入垃圾区
这必然导致R–,也就是我预期出现连续的1–R,R变小了,被你这种重复的值搞的
LeetCode高频题41. 缺失的第一个正数_第8张图片
(4)还有,压根你出现L上的数,竟然比我R还大,你不是扯淡么?
我期待1–R连续出现,都放N长度数组中【而且最次也是R-1位置放R】,你来了一个大于R的数,不够你放啊,你不是捣乱吗?
所以当L处数字大于R也不行

(5)剩下的,就正常,就看看只要是把[L]放到[L-1]位置,继续看L新来的数是否达标
直到L>R,此时的L=R,R+1就是我们那个没法达标的数,也是L+1

手撕代码:

        //定义0--L-1范围中每一个位置i都放好了i+1,说明前面都是合格的
        //与此同时,我们期待1--R范围上的连续数字,出现不合格的数字就让R--,
        public int firstMissingPositiveReview2(int[] nums) {
            int L = 0;
            int R = nums.length;
            while (L < R){
                //L含义不变,0--L-1上,i位置放i+1
                //(1)只要你来到L处,数字放的是L+1,L++,不管,继续
                if (nums[L] == L + 1) L++;
                //(2)但是中间你要是重复出现俩数,那就是不需要的
                //就是[L]本来应该去[L]-1位置,但是你发现[L]-1位置竟然已经是[L]了
                //(3)万一你出现0和负数,也是我们不要的,也不能出现<=L的数,因为我期待L处放L+1,L左边已经好了的
                //(4)还有,压根你出现L上的数,竟然比我R还大,你不是扯淡么?
                else if (nums[L] <= L || nums[L] > R || nums[nums[L] - 1] == nums[L])
                    nums[L] = nums[--R];//直接让R处数过来,不叫换了,否则越界
                //(5)剩下的,就正常,就看看只要是把[L]放到[L-1]位置,继续看L新来的数是否达标
                else swap(nums, L, nums[L] - 1);
            }
            //L=R时,R左边都已经达标了,我们期待的1--R上的数已然出现了,

            return L + 1;
        }

这个方法,绕过了很多交换,也就是上面我那个方法之所以逊色的原因

测试:

    public static void test(){
        Solution solution = new Solution();

        int[] arr = {7,8,9,11,12};
        System.out.println(solution.firstMissingPositive(arr));
        System.out.println(solution.firstMissingPositiveReview(arr));
        System.out.println(solution.firstMissingPositiveReview2(arr));

        int[] arr2 = {1,2,0};
        System.out.println();
        System.out.println(solution.firstMissingPositive(arr2));
        System.out.println(solution.firstMissingPositiveReview(arr2));
        System.out.println(solution.firstMissingPositiveReview2(arr2));

        int[] arr3 = {3,2,-1,4};
        System.out.println();
        System.out.println(solution.firstMissingPositive(arr3));
        System.out.println(solution.firstMissingPositiveReview(arr3));
        System.out.println(solution.firstMissingPositiveReview2(arr3));

        int[] arr4= {1};
        System.out.println();
        System.out.println(solution.firstMissingPositive(arr4));
        System.out.println(solution.firstMissingPositiveReview(arr4));
        System.out.println(solution.firstMissingPositiveReview2(arr4));
    }


    public static void main(String[] args) {
        test();
    }
1
1
1

3
3
3

1
1
1

2
2
2

LeetCode测试:
LeetCode高频题41. 缺失的第一个正数_第9张图片
确实这个方法最牛!!!


总结

提示:重要经验:
0)最重要的是理解,我们预期要1–R上的连续数字,出现不达标的就放垃圾区,R–,合格的L++,否则将L和[L]-1位置交换
1)关键在于我来到i位置,想让i放好i+1,如果不行,我就要把[i]放到[i-1]那里去,交换出来,如果[i]-1位置越界了,直接放到此时的R处,R–
2)这一路,保证0–L上是满足i处放i+1的,否则就往后面交换,然后继续检查L处,当L处是小数或者0,不处理,最后L>R之后,L+1就是那个没法达标的数字
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

你可能感兴趣的:(大厂面试高频题之数据结构与算法,leetcode,缺失的第一个正数,缺失正数,缺失,数组缺第一个正数)