算法题解记录10+++缺失的第一个正数

本文讨论了如何在给定未排序整数数组中找到缺失的最小正整数,提出了一种时间复杂度为O(n)且仅使用常数空间的解决方案,通过排序和双指针技巧,避免了传统方法的O(n^2)复杂度。
摘要由CSDN通过智能技术生成

题目描述:

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

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

示例 1:

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。

示例 2:

输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

示例 3:

输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。

提示:

  • 1 <= nums.length <= 105
  • -2^31 <= nums[i] <= 2^31 - 1

解题准备:

        1.了解基本操作:既然是找到缺失的第一个正数,必然涉及遍历(查找),其它的暂时看不出来。

        2.模拟操作:

        随便提供可能的数据,并得到它们的答案,如下图。

        人眼很快能得到结果,目前看不出步骤。

图1

解题难点1分析:

        难点1:看不出步骤。

        既然我们的目标是找到缺失的第一个正数,那么朴素的思想是:

        方案1:

        定义变量x=1,对数组元素每次遍历,完成遍历后x++,只要存在x不在数组中,那么它就是答案。

        对于【1,2,3……n】这样的数据,最多遍历n*n次即可,所以时间复杂度较高约为O(n^2)

        方案2:

        如果说遍历的效率比较低,我们可以自然地想到利用Hash表,采取空间换时间的思路,来优化算法。

        简单地说,遍历一次数组,把数组的元素全部添加进Hash集合HashSet中,然后利用其方法查找缺失的正数。

        查找的思路和方案1类似,都是定义变量x=1,一遍遍查找,只要x不在集合中,就是答案。

        不过明显地,且不说遍历一次数组,和遍历一次x,一共出现了两次,时间复杂度在O(n^2)

        就单单说使用Hash集合,已经开辟了O(n)的空间复杂度,所以还是有问题。

        当然,效率自然高了很多,只是不符合题意罢了。

解题难点2分析:

        难点2:如何优化算法,保证其效率的同时,又保证其空间复杂度不过高?

        无所谓,先把题目解决了再说,即使时间空间复杂度差一些。

        其实很多人也能想到,既然我们的目标是找到缺失的、最小的正数,那么:

        问题1:如果这个数组有序,不就很好解决了吗?

        比如,对于有序数组【-2,-1,0,1,2,3,4,5,……n】,遍历一遍数组,然后找到不相连的两个数,中间的数不就是答案吗?(当然,0到第一个正数、一直连续到最后一个数、仅一个数的数组、第一个数就比1大等等特殊情况,都值得注意)

        但是这确实是一个思路。

        解决方案也很简单:首先排序一遍【排序的时间复杂度可能很高,但一般不增加空间复杂度】,随后采用遍历方法,找到第一个非0的正数D(如果没有,返回1),判断一下D和1的关系(如果大于1,返回1),随后进入循环,

        判断一下D和下一个数N的关系(如果D+1!=N,说明二者不相连,那么D+1就是答案),如果相连,那么D进一步,直到把整个数组遍历完成。

        这个算法的时间复杂度大约在O(NlogN)左右,由于在原数组基础上排序,所以空间复杂度为O(1),复杂例子可能过不去,但是也是一种思路。

排序代码:

class Solution {
    public int firstMissingPositive(int[] nums) {
        Arrays.sort(nums); // 排序
        int L=0, R=0; // 双指针有助于遍历

        while(R<nums.length && nums[R]<=0 ){
            R++;
        }
        L=R;  // 指向第一个正数
        if(R>=nums.length){
            return 1; // 可能没有正数,如【-2,-1】
        }

        if(L==0){
            if(nums[L]!=1){
                return 1; // 可能第一个数就比1大,如【7,8,9】
            }
        }else if(nums[L-1]<=0 && nums[L]>1){
            return 1; // 可能既有负数,但是第一个正数就比1大,如【-2,-1,5,6】
        }
        if(R<nums.length-1){
            R++; // 保证之后的操作数组不越界
        }

        while(L<nums.length){
            if(nums[L]==nums[R]||nums[L]+1==nums[R]){ // 判断前后是否相连,相连有相同、+1相等两种形式,如【1,1】,【1,2】
                L++;
                if(R<nums.length-1){
                    R++;
                }
            }else{
                return nums[L]+1; // 不相连,返回首个正数
            }
        }

        return nums[L-1]+1; // 遍历结束,全部相连,返回正数
    }
}

解题难点3分析:        

        本题我也是看了答案,如果让我做,我并不能以题目要求做出来,不过提供给我一种新思路:

        本题特殊思路:

        一个算法,其本质是提供一条路线,使输入转变为输出,如果从输入无法解决,不妨从结果集找思路。

        思考“解题难点1分析”中的“方案1”,可以发现这种穷举是最本质的做法,其它的行为都是在其上做优化。

        从结果集入手,可以发现,对于一个长度为n的数组,其缺失的正数只有n+1种可能,分别是从1到n+1。(如果是连续的,即为n+1,如【1,2,3】,该例答案为4)。

        1.那么,我们干脆定义一个辅助数组flag,其大小为n

        2.然后遍历输入数组nums,如果nums【i】满足,>0并且<=n(因为如果>n,它一定不是缺失的最小正数),,就使flag【nums[i]-1】=true;

        3.在遍历结束后,再遍历flag,只要flag【i】==false,就说明数组nums中没有这个正数,这就是答案。

        代码:
class Solution {
    public int firstMissingPositive(int[] nums) {
        boolean[] data=new boolean[nums.length];
        // 不初始化的原因,是JVM自动把boolean类型的数据置为false。

        for(int i=0; i<nums.length; i++){
            if(nums[i]<=nums.length&&nums[i]>0){ // 满足两个条件
                data[nums[i]-1]=true;
            }
        }
        
        for(int i=0; i<nums.length; i++){
            if(!data[i]){
                return i+1; // 返回答案
            }
        }

        // 如果全都是true,说明答案是n+1
        return nums.length+1;
    }
}

真正的答案:

        其实该题要求辅助空间为常数级别,所以解题难点3分析中的答案,也不符合题意,不过已经非常接近了。

        由于题目不限制修改数组nums,所以可以在数组nums的角度上操作,其原理和难点3类似,不过思路又更加特别。

        1.难点3中flag的作用就是映射,将答案映射到第一个false对应的下标上。

        2.其实采用一个全为0、大小为n的数组,同样满足该目的。

        3.不如使其映射到nums上。

        难点4:怎么做到呢?nums中既有正数、又有负数。

        步骤:

        1.负数不影响结果,不妨映射到n+1上(或者0,随你),也就是:遍历nums,如果nums【i】<=0,使nums【i】=n+1或者0。

        2.再一次遍历,正数如果满足nums【i】<=n,那么使nums【nums[i]-1】=0(等于0最好判断)【错误

        问题2:如果nums【i】-1比i更大(比如8和1),那么遍历到i=nums【i】-1即8时,这个数已经被置0了,那怎么处理?

        所以,原数据不能乱变,起码要使得后来的操作可以处理。

        3.删除2,同样的思路,使nums【nums[i]-1】= nums【nums[i]-1】,这保证了可以通过绝对值,获得数据。(这也是最难的一步)

        4.最后遍历一遍数组,和遍历boolean数组一样,找到第一个非负(boolean中是false)的数,这个数下标+1就是答案。

        5.如果全都小于等于0,说明答案是n+1。

代码不在此发布,因为不是我写的。

以上内容即我想分享的关于力扣热题10的一些知识。

        我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值