在数组操作场景中,"去重"是高频需求,而基础去重(完全保留不重复元素)之外,更常见的是"最多保留k个重复元素"的进阶需求。本文将详细讲解一种高效的原地去重思路,仅需一次遍历即可完成,时间复杂度O(n)、空间复杂度O(1),且能灵活扩展至任意保留个数k。
一、问题背景
给定一个有序/无序数组(注:该算法对数组是否有序无要求,核心靠"有效区域"判断重复),要求原地删除多余的重复元素,使得每个元素最多出现2次(可扩展至k次),返回处理后数组的长度,且处理后的元素需保持原数组的相对顺序。
核心约束:不使用额外辅助数组,仅在原数组上操作,追求最优时间和空间效率。
二、核心思路
该算法的核心是**"原地筛选+有效区域标记"**,通过一个变量标记已处理完成的有效区域,再通过简单规则判断当前元素是否能加入有效区域,全程无冗余操作。
1. 核心变量定义
用变量 k 标记"已处理完成的有效区域"的长度,本质是一个尾指针:
-
初始时
k=0,表示有效区域为空(无任何处理完成的元素); -
遍历结束后,数组前
k个元素即为符合要求的结果,k就是结果数组的长度。
2. 遍历判断规则
从左到右遍历数组的每个元素 x,判断 x 是否能加入有效区域,规则如下:
-
情况1:
k < 2(有效区域长度不足2) -
直接将
x放入nums[k]位置,然后k++扩大有效区域。原因是"最多保留2个重复元素",前2个元素无论是否重复,都不会违反规则,无需额外判断。 -
情况2:
k ≥ 2(有效区域长度≥2) -
对比
x与有效区域的"倒数第2个元素"(即nums[k-2]):-
若
x ≠ nums[k-2]:说明有效区域的最后两个元素中,没有和x连续重复超过2次的情况,可加入有效区域(nums[k] = x,k++); -
若
x = nums[k-2]:说明有效区域的最后两个元素已与x相同(如有效区域是 [1,1],x也是1),再加入会出现3个连续重复,直接跳过x,k保持不变。
-
3. 实例演示
以原数组 [1,1,1,2,2,3] 为例,一步步演示执行过程:
-
遍历第1个元素 1:
k=0 < 2,放入nums[0],k=1;有效区域:[1] -
遍历第2个元素 1:
k=1 < 2,放入nums[1],k=2;有效区域:[1,1] -
遍历第3个元素 1:
k=2 ≥ 2,对比nums[0] = 1,x=1相等,跳过;有效区域不变,k=2 -
遍历第4个元素 2:
k=2 ≥ 2,对比nums[0] = 1,x=2不相等,放入nums[2],k=3;有效区域:[1,1,2] -
遍历第5个元素 2:
k=3 ≥ 2,对比nums[1] = 1,x=2不相等,放入nums[3],k=4;有效区域:[1,1,2,2] -
遍历第6个元素 3:
k=4 ≥ 2,对比nums[2] = 2,x=3不相等,放入nums[4],k=5;有效区域:[1,1,2,2,3]
遍历结束,结果数组为前5个元素 [1,1,2,2,3],长度 k=5,完全符合"最多保留2个重复元素"的要求。
三、扩展通用版:最多保留k个重复元素
基础版的思路可直接扩展至"最多保留任意k个重复元素",核心逻辑不变,仅需调整判断规则,通用性极强。
1. 通用核心逻辑
-
前k个元素:直接保留,无需判断。因为"最多保留k个重复元素",前k个元素哪怕全重复,也不会违反规则;
-
第k+1个元素及以后:判断当前元素
x与"有效区域倒数第k个元素"(即nums[k-1 - (k-1)] = nums[有效长度 - k])是否相同:-
不同:加入有效区域,有效长度(k)加1;
-
相同:跳过,不加入有效区域。
-
2. 通用规则验证
以"最多保留3个重复元素"(k=3)为例,原数组 [2,2,2,2,3,3,3]:
-
前3个2:直接保留,有效长度k=3,有效区域 [2,2,2];
-
第4个2:对比有效区域倒数第3个元素(nums[0]=2),相等,跳过;
-
第1个3:对比 nums[0]=2,不相等,加入,k=4,有效区域 [2,2,2,3];
-
第2个3:对比 nums[1]=2,不相等,加入,k=5,有效区域 [2,2,2,3,3];
-
第3个3:对比 nums[2]=2,不相等,加入,k=6,有效区域 [2,2,2,3,3,3];
结果符合"最多保留3个重复元素"的要求,验证了通用规则的正确性。
四、复杂度分析
-
时间复杂度:O(n),其中n为数组长度。仅需从左到右遍历一次数组,每个元素的判断和操作都是O(1)级别的,无嵌套循环;
-
空间复杂度:O(1)。仅使用一个变量k标记有效区域,无其他额外辅助空间,完全原地操作,符合空间最优要求。