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

电商价格列表中 [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;
相关推荐
子兮曰9 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰10 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
倒头就睡的小比特10 小时前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!10 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
前端小万10 小时前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝10 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋10 小时前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
猎头南楼11 小时前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
卡布鲁11 小时前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
旖旎夜光12 小时前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控