算法学习-数组的相关操作,简单的数据结构逆天的难度,扮猪吃老虎(持续更新中)


笔者在刷题的过程中,逐渐发现许多题目是基于数组展开的,在数组上进行排序、双指针等一系列操作,对于整个过程的要求非常高。但无奈由于自己之前程序悟性不高,碰上相关题目也是非常棘手,因此想对自己的刷题路径进行整理,方便大家学习。

相关知识

Java中Arrays常用api
Arrays.fill(char[], '.') //将一个数组全填充一样的值  
Arrays.equals(int[],int[]) //可以比较两数组是否相同
Arrays.asList(nums[i], nums[j], nums[left], nums[right]) //可以返回List
Arrays.sort(int[],Comparator) //默认升序排列  (a,b)->a-b 小的减去大的  重写Comparator只能针对引用类型 参考解析[如何使用Arrays.sort()](https://blog.csdn.net/Forlogen/article/details/106085173)

相关题目

26.删除有序数组中的重复项

left指向当前不重复数字的最后一个,如果不重复一直往后追加,如果重复则right++忽略,需要明确当且仅当nums[left]==nums[right],应该略过当前的nums[right]。追加的策略是先++left,然后将nums[right]补上,最终还是指向最后一个不重复元素。特别的,由于初始化left为0、right为0,第一个必然重复,因此进行忽略保留nums[left],然后right++,下次交换也是先++left

class Solution {
    public int removeDuplicates(int[] nums) {
        int len=nums.length;
        int left=0;
        for(int right=0;right<len;right++){
            if(nums[left]!=nums[right]){
                //left指向当前不重复数字的最后一个,如果不重复一直往后追加,如果重复则right++忽略,针对第一个位置也有效
                //应该是++left,先+1再存
                nums[++left]=nums[right];
            }
        }
        return left+1;
    }
}

上面是比较nums[left]和nums[right]的大小关系,也可以比较nums[right]、nums[right-1]前后的大小关系。left指向不重复数字最后一个的后一个位置。

class Solution {
    public int removeDuplicates(int[] nums) {
        int left=1;
        int len=nums.length;
        //比较前后两个值,找到拐点
        for(int right=1;right<len;right++){
            if(nums[right]!=nums[right-1]){
                nums[left]=nums[right];
                left++;
            }
        }
        return left;
    }
}
80.删除有序数组中的重复项II

相比于26.删除有序数组中的重复项只能保留一个不重复数字,该题最多能够保存重复数字两次。left定义为满足条件的最后一个元素,需要明确当且仅当nums[left-1]==nums[right],应该略过当前的nums[right],由于有序的特点,这种情况下一定有nums[left-1]==nums[left]==nums[right]。当数字长度小于等于2时,一定可以保留。

也可以参考三叶姐的题解,其中left定义不同。

class Solution {
    public int removeDuplicates(int[] nums) {
        int len=nums.length;
        if(len<=2) return len;
        int left=1;
        //left定义为满足要求的最后一个位置
        for(int right=2;right<len;right++){
            if(nums[left-1]!=nums[right]){
                nums[++left]=nums[right];
            }
        }
        //返回数组长度
        return left+1;
    }
}
66.加一

个位数加1,只要从低向高考虑到进位就可以。

class Solution {
    //从末尾开始+1,+1直到没法进位为止,如果全部数位都进位了,需要新构建一个1XXXXX的数组
    public int[] plusOne(int[] digits) {
        int len=digits.length;
        for(int i=len-1;i>=0;i--){
            digits[i]++;
            digits[i]%=10;
            //如果不为0,说明该位没进位,如果为0,则一直往前进位
            if(digits[i]!=0) return digits;
        }

        //如果全部都进位了
        int[]res=new int[len+1];
        res[0]=1;
        return res;
    }
}
75.颜色分类

由于只有0,1,2这三种数字且知道最后它们的排序顺序,因此只需要统计它们出现的次数,最后组成结果,常数空间但用了两趟扫描。

class Solution {
    public void sortColors(int[] nums) {
        int[]fre=new int[3];
        for(int i:nums){
            fre[i]++;
        }
        for(int i=0;i<fre[0];i++){
            nums[i]=0;
        }
        for(int i=fre[0];i<fre[0]+fre[1];i++){
            nums[i]=1;
        }
        for(int i=fre[0]+fre[1];i<nums.length;i++){
            nums[i]=2;
        }
    }
}

尝试用快排partition的思路加上双指针进行优化,将0推向左边,2推向右边。参考liweiwei大神的题解,需要将区间划分先定义好,算法执行过程中保证“循环不变量”,并且初始化时所有区间都为空

执行过程中,需要根据p0、p1的开闭定义,明白其指向的是最后一个位置,还是最后一个位置的后一个位置(待插入位置)。区间定义为[0,p0)p0指向待插入位置,特别的,执行开始时,也可能会出现自身和自身交换的可能性,自旋以后,p0++指向待插入位置。
视频图解

class Solution {
    public void sortColors(int[] nums) {
        int len=nums.length;
        //[0,p0)=0
        //[p0,i)=1
        //[p2,len)=2     
        int p0=0;
        int i=0;
        int p2=len;
        //p2已经被包含在后面了,i==p2就能包含所有区间,因此「循环继续」的条件为i<p2
        while(i<p2){
            //p0是初始化为0的,因此先交换,再++
            if(nums[i]==0){
                swap(nums,i,p0);
                p0++;
                i++;
            }else if(nums[i]==1){
                i++;
            }else{
                //p2是初始化为len的,因此先--,再交换,但是不能保证交换过来的就是1,因此i不变要再判断一次
                p2--;
                swap(nums,i,p2);
            }
        }
    }
    public void swap(int[]nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}
268.丢失的数字

本题可以采用多种方法进行求解,参考宫水三叶的题解
原地哈希法:将当前下标i上的数字交换到下标nums[i]上,但是在交换过后需要再次检查当前位置上被交换过来的元素。由于nums中的所有数字都独一无二,因此可以采用这种一对一的做法。

class Solution {
    public int missingNumber(int[] nums) {
        int len=nums.length;
        for(int i=0;i<len;i++){
            if(nums[i]!=i&&nums[i]<len){
                //先交换当前位置再--,因为循环出去会+1,又能够检查当前位置
                swap(nums,nums[i],i--);
            }
        }
        for(int i=0;i<len;i++){
            if(nums[i]!=i) return i;
        }
        //最后的情况肯定是缺n
        return len;
    }
    public void swap(int[]nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

异或法:任何数同0异或是其本身,任何数和其本身异或是0。充分考察位运算。

class Solution {
    public int missingNumber(int[] nums) {
        int ans=0;
        int len=nums.length;
        for(int i=0;i<=len;i++){
            ans^=i;
        }
        for(int i:nums){
            ans^=i;
        }
        return ans;
    }
}
448.找到所有数组中消失的数字

这题相比于上一题,可能会出现重复的数字,而导致[1,n]范围内有多个数字没出现,并且数据都是正数,采用的方法是进行原地负数标记,即将当前的数字的「绝对值减一」作为下标,将该下标处的数字改为负数,已经改变的就保持负数,最后遍历一次,仍然为正数的「下标加上一」就是未出现的数字。

class Solution {
    //注意可能会由于重复的数字,多个数字未出现
    public List<Integer> findDisappearedNumbers(int[] nums) {
        ArrayList<Integer> ans=new ArrayList<>();
        for(int i=0;i<nums.length;i++){
            nums[Math.abs(nums[i])-1]=-Math.abs(nums[Math.abs(nums[i])-1]);
        }
        for(int i=0;i<nums.length;i++){
            if(nums[i]>0) ans.add(i+1);
        }
        return ans;
    }
}
1403.非递增顺序的最小子序列

贪心做法,先降序处理,然后求出达到总和的一半+1需要的最少数字。

由于Arrays.sort()默认只对int[]数组升序排列,如果要降序排列,不能使用基本类型(int, double, char),如果是int型需要改成Integer,float要改成Float,因此我们进行包装以后排序,然后进行处理。

class Solution {
    public List<Integer> minSubsequence(int[] nums) {
        int len=nums.length;
        Integer[] numsI=new Integer[len];
        for(int i=0;i<len;i++){
            numsI[i]=new Integer(nums[i]);
        }
        Arrays.sort(numsI,(a,b)->b-a);
        ArrayList<Integer> ans=new ArrayList<>();
        int sum=0;
        for(int i:numsI){
            sum+=i;
        }
        //保证大于别的部分
        int target=sum/2+1;
        int index=0;
        while(target>0){
            target-=numsI[index];
            ans.add(numsI[index++]);
        }
        return ans;
    }
}

或者先用默认的降序排列,然后进行颠倒:

class Solution {
    public List<Integer> minSubsequence(int[] nums) {
        int len=nums.length;
        Arrays.sort(nums);
        for(int i=0,j=nums.length-1;i<j;i++,j--){
            int temp=nums[i];
            nums[i]=nums[j];
            nums[j]=temp;
        }
        ArrayList<Integer> ans=new ArrayList<>();
        int sum=0;
        for(int i:nums){
            sum+=i;
        }
        //保证大于别的部分
        int target=sum/2+1;
        int index=0;
        while(target>0){
            target-=nums[index];
            ans.add(nums[index++]);
        }
        return ans;
    }
}
1460.通过翻转子数组使两个数组相等

脑筋急转弯,如果两个数组出现的数字及频率完全相同,那么一定能够通过翻转子数组使得两个数组相等。

class Solution {
    public boolean canBeEqual(int[] target, int[] arr) {
        int len=target.length;
        int[]targeta=new int[1010];
        int[]arra=new int[1010];
        for(int i=0;i<len;i++){
            targeta[target[i]]++;
            arra[arr[i]]++;
        }
        return Arrays.equals(targeta,arra);
    }
}
927.三等分

将数组中的1三等分,记录每组1的第一个位置;按第三组中1到末尾来确定最终每组的二进制数(前导0不重要,但是后面的1后面的0会影响数字大小)。这样只需要比较从每组1的第一个位置开始,是否和第三组的0,1分布一样就行了。

class Solution:
    def threeEqualParts(self, arr: List[int]) -> List[int]:
        one=arr.count(1)
        if one%3:
            return [-1,-1]
        if not one:
            return [0,len(arr)-1]
        cnt,a,b,c,each=0,-1,-1,-1,one//3
        for i,num in enumerate(arr):
            if num:
                cnt += 1
            if a==-1 and cnt:
                a=i
            if b==-1 and cnt>each:
                b=i
            if c==-1 and cnt>2*each:
                c=i
                break
        if (length:=len(arr)-c) and arr[a:a+length]==arr[b:b+length]==arr[c:]:
            return [a+length-1,b+length]
        return [-1,-1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网民工蒋大钊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值