Leetcode刷题——数组题02
题目来源:leetcode
题一:移除元素
题目解读:题目要求原地移除,因此空间复杂度为
O(1)
,这里我们就可以灵活的运用双指针。
代码(C语言版):
int removeElement(int* nums, int numsSize, int val){
int i=0,j=0;
while(i<numsSize){
if(nums[i]==val){
i++;
}else{
nums[j]=nums[i];
i++;
j++;
}
}
return j;
}
代码解读:两个指针都从数组开头开始移动,其中一个指针遍历整个数组记作
i
,另外一个指针记录的都是不等于val
的数记作j
,当指针i
遇到与val
相等的数时,指针i
就继续前进,不相等则将指针i
指向的值赋给指针j
,同时,两个指针都前移,当指针i
走完时,则遍历结束,此时,指针j
之前的数就是所求数组元素。
题二: 删除有序数组中的重复项
题目解读:与上题相类似,同样是原地修改原数组,因此我们同样可以借助双指针。
代码(C语言版):
int removeDuplicates(int* nums, int numsSize){
int i=0;
if(numsSize<=1){
return numsSize;
}
int j = 1;
while(j<numsSize){
if(nums[j]==nums[i]){
j++;
}else{
i++;
nums[i]=nums[j];
j++;
}
}
return i+1;
}
代码解读:双指针的初始位置相邻,分别位于数组的第一位和第二位,因此我们一开始还需要对数组进行特殊情况考虑,当数组中的元素小于或等于1时,就无需去重,直接返回。我们定义前一个指针为
i
,后一个指针为j
,同样,指针i
遍历整个数组,指针j
,指向的都是数组中已经存在的数。比较两个指针对应的数,当两个数相等时,指针i
就继续前进,不相等则将指针i
指向的值赋给指针j
,同时,两个指针都前移,指针j
永远都指向的是符合要求的数中最大的且仅出现过一次的数,后面遍历的数都与之相比较,当指针i
走完时,则遍历结束,此时,指针j
之前的数就是所求数组元素。
题三:合并两个有序数组
题目解读:此题没有空间复杂度的要求,因此通常便可以想到新建一个数组空间,依次将两个数组中的数有序的输进新数组中,最后拷贝到原数组中返回。
代码(C语言版):
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
//生成一个空的数组空间
int* arr = (int*)malloc(sizeof(int)*(m+n));
int i=0,j=0,k=0;
while(i<m&&j<n){
//将较小的放入新数组
arr[k++]= nums1[i]<=nums2[j] ? nums1[i++] : nums2[j++];
}
while(i<m){
arr[k++]= nums1[i++];
}
while(j<n){
arr[k++]= nums2[j++];
}
//复制
memcpy(nums1,arr,sizeof(int)*(m+n));
}
代码解读:采用三个指针,分别指向三个数组的开头位置,依次将两个数组中较小的数放进新数组中,当其中一个数组遍历完,另一个可能还没有结束,此时将没有遍历完的数依次放进新数组中去。最后拷贝到原数组。
优化
我们可以考虑不使用额外的空间,即空间复杂度为O(1)
,题目中有说nums1
数组的初始长度为m+n
,很明显就能猜出出题者的意图,那就是考查 原地修改 ,将空间复杂度降低到O(1)
。因为这样不需要使用额外的数组空间了,我们完全可以把 nums2
也放入 nums1
中。原地修改时,为了避免从前往后遍历导致原有数组元素被破坏掉,我们要选择从后往前遍历!所以,我们总共需要创建三个指针,两个指针用于指向 nums1
和 nums2
的初始化元素数量的末位,也就是分别指向 m−1
和 n−1
的位置,还有一个指针,我们指向 nums1
数组末位即可。既然都知道了,那还等什么,开整!
代码(C语言版):
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
//逆向双指针
int i=m-1,j=n-1,k=m+n-1;
while(i>=0&&j>=0){
nums1[k--] = nums1[i]>=nums2[j] ? nums1[i--] : nums2[j--];
}
while(i>=0){
nums1[k--] = nums1[i--];
}
while(j>=0){
nums1[k--] = nums2[j--];
}
}
图解演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNFoKdk4-1634280964214)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20211015132750775.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JNCH20fA-1634280964220)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20211015133446413.png)]
题四:旋转数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehWVLAgi-1634280964226)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20211015133636748.png)]
题目解读:原题目没有空间复杂度的要求,因此我们可以使用额外的数组将每个数都放到正确的位置去。但它又有进阶的要求,空间复杂度为
O(1)
,难度便提升了一些,这里我们巧妙的利用数组翻转的方法实现。该方法基于如下的事实:当我们将数组的元素向右移动k
次后,尾部k
个元素会移动至数组头部,其余元素向后移动k
个位置。
当n=7,k=3
时:
操作 | 结果 |
---|---|
原始数组 | 1 2 3 4 5 6 7 |
翻转[0,3]区间的元素 | 4 3 2 1 5 6 7 |
翻转[4,6]区间的元素 | 4 3 2 1 7 6 5 |
翻转所有元素 | 5 6 7 1 2 3 4 |
代码(C语言版):
void rotate(int* nums, int numsSize, int k){
k = k%numsSize;
rerver(nums,0,numsSize-k-1);
rerver(nums,numsSize-k,numsSize-1);
rerver(nums,0,numsSize-1);
}
void rerver(int* nums,int a,int b){
while(a<b){
int temp = nums[b];
nums[b] = nums[a];
nums[a] = temp;
a++;
b--;
}
}
题五:数组形式的整数加法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBgSjeII-1634280964227)(C:\Users\HP\AppData\Roaming\Typora\typora-user-images\image-20211015140325086.png)]
题目解读:该题我们采用逐位相加来思考,需要考虑到进位的记录,及最后数组的返回方式。
代码(C语言版):
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* addToArrayForm(int* num, int numSize, int k, int* returnSize){
//求出整数k的位数
int kNum = k,kSize = 0;
while(kNum){
kSize++;
kNum/=10;
}
//生成新的数组空间,大小为位数较大的那个再加一,因为两数相加最多溢出一位
int len = numSize > kSize ? numSize : kSize;
int* arr = (int*)malloc(sizeof(int)*(len+1));
//定义初始进位默认为0
int addNum = 0;
int aSize = 0;
int i = numSize-1;
while(len--){
int a = 0;
if(i>=0){
a = num[i];
i--;
}
int res = a + k%10 + addNum;
k /=10;
if(res > 9){
res -= 10;
addNum = 1;
}else{
addNum = 0;
}
arr[aSize++] = res;
}
if(addNum==1){
arr[aSize++] = 1;
}
//逆置数组
rerver(arr,0,aSize-1);
*returnSize = aSize;
return arr;
}
void rerver(int* nums,int a,int b){
while(a<b){
int temp = nums[b];
nums[b] = nums[a];
nums[a] = temp;
a++;
b--;
}
}
代码解读:最终我们需要返回一个新的数组,因此我们便会考虑到新开辟的数组所占的空间大小,已知两个数相加最多比位数较多的那一位溢出一位,因此我们新开辟的数组长度要求比两个数位数较大的那一位多一位,数组的位数很容易就知道,整数的位数可以通过每次除10,通过可以除的次数判断该整数的位数。开辟好空间后,定义两个指针分别指向整数个位和数组的最后一位,整数通过每次除10后再与10取余得到每位的数,数组则可以通过指针前移实现。每次相加将结果依次放入新数组中,但加法我们要考虑到逢十进一的情况,因此在插入到新数组前我们通过
addNum
来判断是否需要进1,需要则把该值记录为1,下一位计算时加上if(i>=0){a = num[i];i--;}
这段代码的作用在于当数组长度小于整数长度时,当整数还在进行取位计算时,数组的索引已经越界来,于是,我i吗便将越界后从数组中取到的值都自动设置为1.if(addNum==1){arr[aSize++] = 1;}
这段代码的作用在于倘若我们进行最后一位相加的时候还需要进位,可此时遍历已经结束,这时,我们直接将1插入到数组的下一位。最后我们得到的数组再进行逆置操作便是最终结果了。