数组去重算法:理论与实践深度解析

引言/概述

数组去重是前端开发中常见的基础问题,也是面试中高频出现的手写代码题目。 本学习笔记系统地探讨了多种数组去重的实现方式,从基础的双重循环到现代的 Set 数据结构,覆盖了不同时间复杂度和空间复杂度的解决方案。 通过理论与实践相结合的方式,深入理解算法优化的核心思想。


核心概念与理论要点

1. 数组去重的定义与应用场景

数组去重是指从包含重复元素的数组中提取出唯一元素的过程。在实际业务中,数组去重常用于:

  • 数据清洗与预处理
  • 避免重复操作(如避免重复请求、重复渲染)
  • 统计分析中的去重计数

2. 关键数组 API

API 功能描述 返回值
Array.isArray(arr) 检查参数是否为数组 Boolean
arr.indexOf(item) 返回元素首次出现的下标 Number(-1表示不存在)
arr.lastIndexOf(item) 返回元素最后出现的下标 Number(-1表示不存在)
arr.filter(callback) 过滤数组元素 新数组(包含返回true的元素)
arr.sort() 数组排序(默认字典序) 原数组(会修改原数组)
arr.reverse() 反转数组 原数组

3. 复杂度分析理论

算法的性能通常用时间复杂度和空间复杂度来衡量:

  • O(n²):双重循环结构,外层遍历每个元素,内层检查是否重复
  • O(n log n):先排序(O(n log n))再去重(O(n)),总体由排序主导
  • O(n):空间换时间策略,利用哈希表/对象实现 O(1) 查找

代码实现与分析

方法一:双重循环法(O(n²))

js 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.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;
}

实现思路:维护一个结果数组,对每个新元素遍历结果数组检查是否重复。

复杂度:时间 O(n²),空间 O(n)(最坏情况)


方法二:indexOf 方法(O(n²))

js 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.error('输入必须是数组');
        return [];
    }
    let res = [];
    for (let i = 0; i < arr.length; i++) {
        if (res.indexOf(arr[i]) === -1) {
            res.push(arr[i]);
        }
    }
    return res;
}

实现思路 :利用 indexOf 方法替代内层循环,代码更简洁,但本质仍是 O(n²)。


方法三:filter + indexOf(O(n²))

js 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.error('输入必须是数组');
        return [];
    }
    return arr.filter(function(item, index) {
        return arr.indexOf(item) === index;
    });
}

实现思路 :利用 filter 的过滤特性,只保留首次出现的元素(indexOf 返回的索引等于当前索引)。


方法四:排序后去重(O(n log n))

js 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.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;
}

实现思路:先排序使重复元素相邻,再遍历比较相邻元素。

注意sort() 默认按字典序排序,对数字排序需额外处理。


方法五:哈希表法(O(n))

js 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.error('输入必须是数组');
        return [];
    }
    let res = [], 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;
}

实现思路:利用对象字面量作为哈希表,将数组元素作为 key,实现 O(1) 时间复杂度的存在性检查。

复杂度:时间 O(n),空间 O(n)


方法六:Set 数据结构(O(n))

js 复制代码
function unique(arr) {
    if (!Array.isArray(arr)) {
        console.error('输入必须是数组');
        return [];
    }
    return [...new Set(arr)];
}

实现思路 :ES6 新增的 Set 数据结构天然不允许重复值,配合扩展运算符 ... 转换回数组。

复杂度:时间 O(n),空间 O(n)


关键发现与心得

1. 时间复杂度的递进关系

方法 时间复杂度 空间复杂度 特点
双重循环 O(n²) O(n) 基础易懂,性能较差
indexOf O(n²) O(n) 代码简洁,本质仍是双重循环
filter+indexOf O(n²) O(n) 函数式编程风格
sort+去重 O(n log n) O(n) 排序后比较,性能提升
哈希表 O(n) O(n) 空间换时间,最优时间复杂度
Set O(n) O(n) ES6 语法糖,简洁高效

2. 参数校验的重要性

所有实现都包含了 Array.isArray() 检查,体现了代码健壮性原则:

  • 防止非数组输入导致运行时错误
  • 提供友好的错误提示
  • 返回空数组作为安全默认值

3. 空间换时间的思想

哈希表法和 Set 方法通过额外的空间开销(O(n))换取了时间复杂度的优化(O(n)),这是算法设计中的经典权衡策略。

4. ES6 特性的优势

Set 数据结构的引入极大简化了去重逻辑,一行代码即可完成,体现了语言进化带来的开发效率提升。


总结与展望

学习总结

通过系统学习数组去重的多种实现方式,我们掌握了:

  1. 基础算法思想:双重循环、排序比较、哈希查找
  2. 复杂度分析方法:时间复杂度与空间复杂度的权衡
  3. 代码健壮性原则:参数校验、错误处理
  4. 语言特性应用:ES6 Set、扩展运算符、高阶函数

实践建议

在实际开发中选择去重方法时,可参考以下原则:

  • 小规模数据 :任意方法均可,推荐使用 Set 保持代码简洁
  • 大规模数据:优先选择 O(n) 复杂度的哈希表法或 Set 方法
  • 需要保持原顺序:避免使用排序法,选择哈希表或 Set
  • 兼容性要求:ES5 环境使用哈希表法,ES6+ 环境使用 Set

未来扩展

数组去重问题可以进一步扩展:

  • 对象数组去重:根据对象属性进行去重
  • 复杂数据结构:处理嵌套数组、函数等特殊类型
  • 性能优化:针对特定数据分布优化算法

相关推荐
带多刺的玫瑰3 分钟前
Leecode#4刷题之寻找两个正序数组的中位数
java·前端·算法
土司大王12 分钟前
LeetCode hot100——除了自身以外数组的乘积
数据结构·算法·leetcode
IvanCodes14 分钟前
RAG 实战教程(三):向量数据库检索算法,KNN、IVF、HNSW 与 Faiss 实战
人工智能·算法·agent
常宇佳21 分钟前
vue3 实战系列之无法识别vue组件
前端·javascript·vue.js
阿无,28 分钟前
布隆过滤器
java·算法·哈希算法
徒慕风流34 分钟前
激光雷达回波信号滤波与时刻鉴别算法:原理、代码实现与仿真验证
python·算法·激光雷达
这料鬼有毒39 分钟前
二刷hot100-70.爬楼梯
算法
全栈项目管理程序猿40 分钟前
ArcGIS JS 基础教程(12):视图截图 takeScreenshot
javascript
小棉花的ai跨境之旅1 小时前
Codex 子 Agent 分工实战:Sol 当军师、Luna 当搬砖工,单任务成本压到 $0.61
javascript
daols881 小时前
vue 实现基于 vxe-table 构建多维度产品对比表
前端·javascript·vue.js