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

引言/概述

数组去重是前端开发中常见的基础问题,也是面试中高频出现的手写代码题目。 本学习笔记系统地探讨了多种数组去重的实现方式,从基础的双重循环到现代的 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

未来扩展

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

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

相关推荐
XinZong3 小时前
一起来聊聊?OpenClaw 的 Skill 是提效的技能工具,还是又一个吃灰的 App 柜?
javascript
CS创新实验室3 小时前
数据结构和算法:摊还分析
java·数据结构·算法
curry____3033 小时前
邻接矩阵 和 领接表 和 链式前向星对比
数据结构·c++·算法
卷帘依旧4 小时前
Transpiler和Polyfill分别是什么作用
javascript
通信小呆呆4 小时前
维度分数傅里叶时频图 + 图神经网络:突破传统时频分析的目标识别与杂波抑制新框架
人工智能·神经网络·算法
Amazing53074 小时前
docker compose 漏一个参数全失效
后端·代码规范
csdn_aspnet4 小时前
C++ 算法 LeetCode 编号 70 - 爬楼梯
开发语言·c++·算法·leetcode
he___H4 小时前
leetcode100-合并区间
java·数据结构·算法
wuweijianlove4 小时前
算法性能优化中的数据流重构与依赖消解的技术6
算法