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