举一反三:删除排序数组重复项

电商价格列表中 [19,19,23,23,23,56] 因重复数据导致价格展示异常,你需要在不创建新数组的前提下,用O(1)空间复杂度实现去重后渲染UI


问题本质与挑战

需求核心 :在已排序数组 中原地删除重复项,返回去重后长度
四大挑战

  1. 空间限制:O(1)额外空间(禁止新建数组)
  2. 原地操作:直接修改原数组
  3. 顺序保持:保留非重复元素原始顺序
  4. 性能要求:时间复杂度O(n)

双指针法(首选)

javascript 复制代码
const removeDuplicates = (nums) => {
    if (nums.length === 0) return 0;
    
    // 慢指针:记录唯一元素应插入位置
    let slow = 0; 
    
    // 快指针:扫描所有元素
    for (let fast = 1; fast < nums.length; fast++) {
        // 发现新唯一元素 → 慢指针前移并更新值
        if (nums[fast] !== nums[slow]) {
            slow++;
            nums[slow] = nums[fast];
        }
    }
    return slow + 1; // 返回唯一元素总数量
};

运行过程动态演示 (输入 [1,1,2,3,3]):

graph TB A[初始] --> |nums| B[1,1,2,3,3] B --> C[slow=0, fast=1] C -- "1==1 → 跳过" --> D[fast++] D -- "fast=2: 1!=2 → 赋值" --> E[slow=1, nums=1,2,2,3,3] E -- "fast=3: 2!=3 → 赋值" --> F[slow=2, nums=1,2,3,3,3] F -- "fast=4: 3==3 → 跳过" --> G[结束] G --> H[长度 = slow+1 = 3]

关键代码解析

  1. 双指针分工
    • slow:唯一元素边界(已处理区域的终点)
    • fast:探测器(扫描未处理区域)
  2. 位移条件
    nums[fast] !== nums[slow] 表明发现新唯一元素
  3. 赋值操作
    slow++; nums[slow] = nums[fast] 完成唯一元素前移
  4. 返回值
    slow + 1 唯一元素个数(慢指针索引+1)

双指针法 vs 传统方案

方案 时间复杂度 空间复杂度 是否原地修改 适用场景
双指针法 O(n) O(1) 内存敏感的排序数组
Set去重 O(n) O(n) 简单场景/小数据量
filter创建新数组 O(n) O(n) 可接受新数组的场景
排序后暴力删除 O(n²) O(1) 无序数组(先排序)

💡 为什么双指针是王者?

  1. 内存优势:不创建新数组,减少GC压力(尤其处理10万+商品列表时)
  2. 性能优势:仅单次遍历,避免嵌套循环
  3. 符合React规范 :直接修改状态数组,避免setState额外开销

举一反三:双指针的三大变种应用

变种1:保留K个重复项(电商价格保留2次折扣)

javascript 复制代码
// 保留最多2个相同元素 输入 [1,1,1,2,2,3] → 返回5(新数组[1,1,2,2,3])
const removeDuplicatesKeepTwo = (nums) => {
    if (nums.length <= 2) return nums.length;
    
    let slow = 2; // 从第三位开始检查
    for (let fast = 2; fast < nums.length; fast++) {
        // 核心:当前元素 ≠ 慢指针前2位的元素
        if (nums[fast] !== nums[slow - 2]) {
            nums[slow] = nums[fast];
            slow++;
        }
    }
    return slow;
};

变种2:删除特定值(清除所有0值)

javascript 复制代码
// 输入 [0,5,0,7] → 返回2(新数组[5,7])
const removeZero = (nums) => {
    let slow = 0;
    for (let fast = 0; fast < nums.length; fast++) {
        if (nums[fast] !== 0) {
            nums[slow] = nums[fast];
            slow++;
        }
    }
    return slow;
};

变种3:有序数组合并(商品价格区间合并)

javascript 复制代码
// 合并两个有序数组(LeetCode88题核心逻辑)
const merge = (nums1, m, nums2, n) => {
    let p1 = m - 1, p2 = n - 1, tail = m + n - 1;
    while (p2 >= 0) {
        // 逆序比较插入尾部
        nums1[tail--] = (p1 >= 0 && nums1[p1] > nums2[p2]) 
                         ? nums1[p1--] 
                         : nums2[p2--];
    }
};

💻 前端实战:Vue中高效渲染去重数据

html 复制代码
<template>
  <!-- 商品价格列表渲染 -->
  <div v-for="(price, index) in displayPrices" :key="index">
    {{ price }}元
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 从API获取的含重复价格
      prices: [19,19,23,23,56] 
    };
  },
  computed: {
    displayPrices() {
      const uniqueCount = this.removeDuplicates(this.prices);
      // slice截取去重后部分
      return this.prices.slice(0, uniqueCount);
    }
  },
  methods: {
    removeDuplicates(nums) {
      // 双指针去重算法(同上文)
    }
  }
};
</script>

优化关键点

  1. 将算法封装为methods避免重复计算
  2. 使用computed属性缓存结果
  3. slice(0, N)获取有效数据避免渲染冗余元素

⚠️ 边界陷阱与防御编码

  1. 空数组处理
javascript 复制代码
if (nums.length === 0) return 0; // 避免fast指针越界
  1. 非排序数组防御
javascript 复制代码
// 开发环境检测
if (process.env.NODE_ENV === 'development') {
  for (let i = 1; i < nums.length; i++) {
    if (nums[i] < nums[i-1]) throw new Error("输入必须为排序数组!");
  }
}
  1. 非数字类型处理
javascript 复制代码
if (typeof nums[fast] !== 'number') {
  console.warn('非数字元素:', nums[fast]);
  continue;
}

工程建议

  1. 大数据量优化

    • 超过10万数据时使用 Web Worker 避免阻塞UI
    javascript 复制代码
    // 主线程
    const worker = new Worker('dedupe-worker.js');
    worker.postMessage(prices);
  2. 与后端协作

    • 在SQL查询用 DISTINCT 初步去重减少前端压力
    sql 复制代码
    SELECT DISTINCT price FROM products ORDER BY price;
相关推荐
Kiri霧1 分钟前
Kotlin抽象类
android·前端·javascript·kotlin
chenjazz22 分钟前
算法基础知识总结
数据结构·算法·排序算法
秋风战士25 分钟前
通信算法之294:LTE系统中的整数倍频偏估计
人工智能·python·算法
zaiyang遇见27 分钟前
P1205 [USACO1.2] 方块转换 Transformations
数据结构·算法·模拟·信息学奥赛·程序设计竞赛·usaco·完全搜索
理论最高的吻28 分钟前
最终分配算法【论文材料】
算法
冷月葬花~30 分钟前
day25 力扣90.子集II 力扣46.全排列 力扣47.全排列 II
数据结构·算法·leetcode
nako_sayuri31 分钟前
可获得的最大点数
算法
ai小鬼头34 分钟前
创业小公司如何低预算打造网站?熊哥的实用建站指南
前端·后端
珂朵莉MM34 分钟前
2021 RoboCom 世界机器人开发者大赛-本科组(初赛)解题报告 | 珂学家
人工智能·python·算法·职场和发展·机器人
阿星做前端40 分钟前
聊聊前端请求拦截那些事
前端·javascript·面试