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

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

为什么需要数组去重?

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

  • 前端处理后端返回的数据,需要去掉重复的选项
  • 用户操作产生的一系列 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)
  • 懂得权衡时间与空间
  • 关注代码的可读性与健壮性

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

相关推荐
老余捞鱼2 分钟前
线性回归实战:5步验证你的量化因子是否真有效
算法·金融·回归·线性回归·ai量化
想吃火锅10055 分钟前
【leetcode】121.买卖股票的最佳时机js/c++
算法·leetcode·职场和发展
码云数智-大飞29 分钟前
RAII 与智能指针深度拆解
java·前端·算法
Dick50737 分钟前
ROS2 常用命令表
人工智能·学习·算法·机器人
apcipot_rain1 小时前
计科八股20260616(2)/面经——线性代数对称阵求n次幂、概率论最大似然估计
算法
cici158742 小时前
彩色图像模糊增强(Fuzzy Enhancement)MATLAB 实现
开发语言·算法·matlab
宝贝儿好2 小时前
【LLM】第二章:HuggingFace入门学习
人工智能·深度学习·神经网络·学习·算法·自然语言处理
凌波粒2 小时前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
啵啵啵鱼2 小时前
数组---完
算法·排序算法
嘿黑嘿呦2 小时前
chap 8排序
算法·蓝桥杯·排序算法·软件工程