本文对使用和操作数组时常遇到的一类数组元素移除问题的高效解决方法(O(N))做一总结,并对其中用到的思想做一些总结和延伸。本文以leetcode中的三道算法题为例进行说明。
无序数组中指定元素的移除
题目链接:移除元素
问题简述:使用原地算法移除数组中的指定元素target
分析
若使用数组元素覆盖的方式,最坏情况为数组元素全部为target
,逐个覆盖的时间复杂度为O(N^2)
,这里不予讨论。
若要在线性时间复杂度解决这个问题,可以最先想到利用空间换时间的方法,另外开辟一个数组,择原数组中的非target
元素放入新数组,即可完成target
元素的移除,但是这种方法不满足空间复杂度O(1)
的要求,但是可以沿用其中的思想。
具体做法为:双指针(res
, cur
)遍历数组,若cur
指针指向的元素为targrt
元素,则右移cur
;否则将nums[cur]
赋给nums[res]
以实现对target
元素的覆盖。可以发现,此时res
左侧的数组总是不含target
元素的数组。
代码
int removeElement(int* nums, int numsSize, int val)
{
int cur = 0;
int res = 0;
while(cur < numsSize)
{
if(nums[cur] != val)
{
nums[res++] = nums[cur];
}
cur++;
}
return res;
}
有序数组中重复元素的部分移除
题目链接:删除有序数组中的重复项
问题简述:对有序数组中的元素进行原地去重操作,使重复的元素最终只保留一个
分析
类似上一个问题,此问题也可以另外开辟一个数组resArr
,使用两个指针分别遍历两个数组,通过比较两个指针指向两个数组的元素可以决定是否将原数组中的元素移动到resArr
中,以实现去重操作。沿用这个思路,可以另外使用常数空间复杂度的方法原地解决这个问题。
具体做法为:双指针(res
, cur
)遍历数组,若res
指针指向的元素与cur
指针指向的元素相等,则右移cur
;否则将nums[cur]
赋给nums[res]
,以实现非重复元素的左移。可以发现,此时res
指针的左侧总不包含重复元素。
代码
int removeDuplicates(int* nums, int numsSize)
{
int res = 0;
int cur = 0;
while(cur < numsSize)
{
if(nums[cur] == nums[res]) {
cur++;
}
else {
nums[++res] = nums[cur++];
}
}
return res + 1;
}
一个延伸:合并两个有序数组
题目链接:合并两个有序数组
问题简述:在nums1
中合并两个非递减数组nums1
和nums2
分析
若要合并nums1
和num2
于另一个数组中,则可以这样做:从头遍历num1
和num2
,不断择较小值放入resArr
,则最后的retArr
即为合并后的有序数组。
在这个问题中,要求在nums1
中进行合并,则可以考虑将nums1
后面的空余空间视为retArr
,从后向前遍历nums1
的有效数据部分和nums2
,择较大值放入nums1
以进行覆盖,最后得到的nums1
即为所要求的数组。注意当最后nums2
有剩余时,要将nums2
的剩余部分覆盖到nums1
。
代码
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)标签:cur,nums,int,res,元素,数组,移除,nums1 From: https://blog.51cto.com/158SHI/6084376
{
int end1 = m - 1;
int end2 = n - 1;
int end = m + n - 1;
while(end1 >= 0 && end2 >= 0)
{
if(nums1[end1] > nums2[end2]) {
nums1[end--] = nums1[end1--];
}
else {
nums1[end--] = nums2[end2--];
}
}
while(end2 >= 0)
{
nums1[end--] = nums2[end2--];
}
}