数组去重:从零开始,写一个靠谱的工具函数

一道经典面试题,七种实现思路,带你理解时间复杂度的演进

为什么需要数组去重?

日常开发中,我们经常遇到这样的场景:

  • 前端处理后端返回的数据,需要去掉重复的选项
  • 用户操作产生的一系列 ID,需要过滤重复项
  • 数据统计前需要清洗重复值

数组去重虽然基础,但不同的实现方式体现了对 JavaScript 语言理解的深度。

代码是写给人看的,只是顺便能在机器上运行

实际开发中,一段代码往往会被多个人维护。过了一周、一个月,你或者同事可能早就忘了当初为什么这么写。

好注释的价值:

  • 有利于协作 ------ 开发者和使用者不是一个人
  • 防止遗忘 ------ 注释是代码的一部分
  • 提高可读性 ------ 看注释就能理解意图

代码层面的原则:

  • 一个函数只做一个功能
  • 复杂逻辑要封装
  • 函数要有健壮性,主动检验参数

每次第一步:参数校验

JavaScript 是动态弱类型语言,调用者可能传进来任何东西:数组、字符串、数字、甚至是 null

参数校验是最容易被忽略的"基础功":

javascript

javascript 复制代码
// 错误示范:没有校验,直接使用
function unique(arr) {
    return [...new Set(arr)];  // 如果 arr 不是数组,直接报错
}

基础版本:双重循环

最直观的思路:维护一个新数组,遍历原数组,判断每个元素是否已经存在于新数组中。

javascript

ini 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error');
        return [];
    }

    let res = [arr[0]];
    for (let i = 1; i < arr.length; i++) {
        let flag = true;
        for (let j = 0; j < res.length; j++) {
            if (arr[i] === res[j]) {
                flag = false;
                break;
            }
        }
        if (flag) {
            res.push(arr[i]);
        }
    }
    return res;
}

console.log(unique([1, 2, 3, 2, 5])); // [1, 2, 3, 5]

时间复杂度 :O(n²),两层循环嵌套。
缺点:数据量大时性能较差。

改进一:利用 indexOf

indexOf 替代内层循环,代码更简洁,但时间复杂度仍是 O(n²)。

javascript

ini 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error');
        return [];
    }

    const res = [];
    for (let i = 0; i < arr.length; i++) {
        if (res.indexOf(arr[i]) === -1) {
            res.push(arr[i]);
        }
    }
    return res;
}

改进二:filter + indexOf

利用数组的 filter 方法,代码更函数式。核心逻辑:当前元素第一次出现的位置等于当前位置才保留。

javascript

javascript 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error');
        return [];
    }

    return arr.filter((item, index) => {
        return arr.indexOf(item) === index;
    });
}

注意indexOf 会返回元素第一次出现的位置,如果当前索引不是第一次出现,说明是重复项,被过滤掉。

改进三:排序后去重

先排序,然后比较相邻元素是否相同。

javascript

ini 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error');
        return [];
    }

    arr = arr.sort();
    let res = [arr[0]];
    for (let i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i - 1]) {
            res.push(arr[i]);
        }
    }
    return res;
}

console.log(unique([1, 2, 5, 3, 2])); // [1, 2, 3, 5]

时间复杂度 :O(n log n),主要来自排序。
注意:排序会改变元素顺序,如果不需要保持原顺序,这是不错的选择。

改进四:空间换时间 ------ 对象字面量

利用对象属性的唯一性,将元素作为对象的 key,实现 O(n) 时间复杂度。

javascript

ini 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error');
        return [];
    }

    const res = [];
    const obj = {};
    
    for (let i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            res.push(arr[i]);
            obj[arr[i]] = 1;
        } else {
            obj[arr[i]]++;
        }
    }
    return res;
}

时间复杂度 :O(n)
空间复杂度:O(n),用额外的对象存储已出现的元素。

这种方式是典型的"用空间换时间"策略。

改进五:Set ------ 最优雅的解法

ES6 新增的 Set 数据结构,天然保证元素不重复。一行代码解决问题。

javascript

javascript 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error');
        return [];
    }

    return [...new Set(arr)];
}

console.log(unique([1, 2, 5, 3, 2])); // [1, 2, 5, 3]

原理 :Set 类似于数组,但成员的值都是唯一的。展开运算符 ... 将 Set 转回数组。
时间复杂度 :O(n)
适用性:现代浏览器和 Node.js 环境均可使用。

方法对比

方法 时间复杂度 空间复杂度 是否保持顺序 代码复杂度
双重循环 O(n²) O(n)
indexOf O(n²) O(n)
filter+indexOf O(n²) O(n)
排序去重 O(n log n) O(n)
对象标记 O(n) O(n)
Set O(n) O(n) 最低

开发建议

  1. 日常开发 :首选 Set 方式,简洁且性能好
  2. 需要兼容旧浏览器:用对象标记法
  3. 不在意顺序:排序去重也是好选择
  4. 务必做参数校验:判断是否为数组,增强代码健壮性

javascript

javascript 复制代码
// 健壮性示例
if (!Array.isArray(arr)) {
    throw new TypeError('Expected an array');
}

总结

从双重循环到 Set,体现了编程思想的演进:

  • 学会用语言提供的 API(indexOf、filter)
  • 理解不同数据结构的特性(对象、Set)
  • 懂得权衡时间与空间
  • 关注代码的可读性与健壮性

一道简单的面试题,其实考察了很多底层能力。你学会了吗?

相关推荐
通信小呆呆15 小时前
维度分数傅里叶时频图 + 图神经网络:突破传统时频分析的目标识别与杂波抑制新框架
人工智能·神经网络·算法
csdn_aspnet15 小时前
C++ 算法 LeetCode 编号 70 - 爬楼梯
开发语言·c++·算法·leetcode
he___H15 小时前
leetcode100-合并区间
java·数据结构·算法
CeshirenTester15 小时前
大厂校招变了:AI 能力正在进入笔试和面试
人工智能·面试·职场和发展
神奇小汤圆15 小时前
四种索引,一个系统,重新定义 AI 如何理解知识
面试
wuweijianlove16 小时前
算法性能优化中的数据流重构与依赖消解的技术6
算法
Agent手记16 小时前
智能财务对账Agent如何设计?2026金融大模型Agent架构设计与实战指引
人工智能·算法·ai·金融
韩小兔修媛史16 小时前
SpringBoot面试八股文(持续更新)
spring boot·后端·面试
计算机安禾16 小时前
【算法分析与设计】第5篇:最大子数组问题:分治与线性扫描的对比分析
算法
AI人工智能+电脑小能手16 小时前
【大白话说Java面试题 第74题】【Mysql篇】第4题:InnoDB 和 MyISAM 的数据文件存储区别?
java·开发语言·mysql·面试