JS无法真正原地去重,但可通过双指针覆盖+修改length实现逻辑原地去重:慢指针存新元素位置,快指针遍历,最后截断length。

JS 中无法真正实现“原地去重”(即完全不分配新数组、仅修改原数组长度和元素位置),因为数组是动态的,但可以通过 复用原数组空间 + 修改 length 属性 实现逻辑上的原地去重,避免创建新数组,显著节省内存。
利用双指针覆盖 + length 截断
这是最常用且内存最优的方式:用一个慢指针记录去重后应存放的位置,快指针遍历原数组;遇到新元素就覆盖到慢指针位置,最后将数组 length 设为慢指针值。
适用于已排序或可排序的数组(如数字、字符串):
function dedupeInPlaceSorted(arr) {
if (arr.length // 示例
const nums = [1, 1, 2, 2, 3, 4, 4, 5];
dedupeInPlaceSorted(nums); // [1, 2, 3, 4, 5]
console.log(nums.length); // 5 —— 内存已收缩
对未排序数组做原地去重(带 Set 辅助)
未排序时需判重,必须用哈希结构(如 Set)记录已见元素。虽然 Set 占用额外 O(n) 空间,但不创建新数组,仍比 [...new Set(arr)] 节省内存(后者隐式分配新数组)。
关键点:只在原数组上移动元素,最后截断 length:
- 用 Set 记录已出现的值
- 用 write 指针指向当前可写位置
- 遍历中首次遇到的值,复制到
arr[write++] - 遍历完立即设置
arr.length = write
function dedupeInPlace(arr) {
if (arr.length // 示例
const mixed = ['a', 'b', 'a', 'c', 'b'];
dedupeInPlace(mixed); // ['a', 'b', 'c']
console.log(mixed.length); // 3
注意事项与边界优化
原地去重不是银弹,需注意以下实际细节:
-
引用类型需谨慎:若数组含对象,
===或Set默认按引用判重;如需深比较,必须手动实现并接受性能损失,且无法真正“原地”(因需序列化或遍历字段) -
length 截断是关键一步:不设
arr.length = newLen,旧元素仍存在于内存中(只是不可索引),V8 等引擎不会自动回收这部分空间 -
稀疏数组不适用:
delete arr[i]会制造空洞,无法靠 length 收缩;原地去重要求数组是密集的(dense) - 避免副作用:确保输入数组允许被修改;生产环境建议加只读检查或文档说明
对比:为什么不用 filter + new Set?
像 arr.filter((x, i) => arr.indexOf(x) === i) 或 [...new Set(arr)] 都会新建数组,触发额外内存分配和 GC 压力。而原地版本只操作原数组索引与 length,在大数据量(如数万项以上)或内存受限环境(如嵌入式 JS、WebWorker)中优势明显。











