← 返回手写题目录

📊 数组手写题 Top 10

去重 · 扁平化 · 集合运算 · 分块 · 分组 · 排序 · 查找 · 最大和

基础高频

1数组去重(基础值去重 + 对象按字段去重)

场景:列表过滤重复数据。基本类型去重要秒答,对象去重要会用 Map。

// ① 基础值去重 —— 三种方法 // 方法1:Set(最简洁,推荐) const unique = arr => [...new Set(arr)] unique([1, 2, 2, 3, 3]) // [1,2,3] // 方法2:filter + indexOf const unique2 = arr => arr.filter((v, i) => arr.indexOf(v) === i) // 方法3:reduce + includes(兼容性好) const unique3 = arr => arr.reduce((acc, v) => { if (!acc.includes(v)) acc.push(v) return acc }, []) // ② 对象数组按某字段去重(业务高频!) // 场景:[{id:1,name:'a'},{id:2,name:'b'},{id:1,name:'c'}] → 按 id 去重 function uniqueBy(arr, key) { const map = new Map() arr.forEach(item => { if (!map.has(item[key])) map.set(item[key], item) }) return [...map.values()] } const users = [ { id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 1, name: 'c' } ] uniqueBy(users, 'id') // [{id:1,name:'a'},{id:2,name:'b'}]
Map 优势:键可以是任意值,查找 O(1)。比 filter+find(O(n²))高效。
2数组扁平化(多维 → 一维)

场景:[1, [2, [3, [4]]]] → [1,2,3,4]。手写必考。

// 方法1:原生 flat(生产用) [1, [2, [3]]].flat(Infinity) // [1,2,3] // 方法2:递归 + reduce(手写必会) function flatten(arr) { return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val) , []) } // 方法3:扩展运算符(迭代展开,不用递归) function flattenIter(arr) { while (arr.some(v => Array.isArray(v))) { arr = [].concat(...arr) } return arr } // 方法4:toString(仅限数字数组,了解即可) [1, [2, [3]]].toString().split(',').map(Number) // [1,2,3]
面试推荐:方法2(reduce 递归)。如果加参数 depth 控制深度,更接近原生 flat 实现。
3数组并集、交集、差集

场景:权限合并、标签筛选、对比差异。

const a = [1, 2, 3], b = [2, 3, 4] // 并集:A ∪ B(合并去重) const union = (a, b) => [...new Set([...a, ...b])] union(a, b) // [1,2,3,4] // 交集:A ∩ B(两边都有的) const intersect = (a, b) => { const setB = new Set(b) return [...new Set(a)].filter(x => setB.has(x)) } intersect(a, b) // [2,3] // 差集:A - B(A 有但 B 没有) const difference = (a, b) => { const setB = new Set(b) return [...new Set(a)].filter(x => !setB.has(x)) } difference(a, b) // [1] // 对称差:A △ B(只在一边有的,并集 - 交集) const symmetricDiff = (a, b) => difference(union(a, b), intersect(a, b)) symmetricDiff(a, b) // [1,4]
套路:先转 Set 加速查找 O(1),再 filter。直接 a.filter(x => b.includes(x)) 是 O(n²),性能差。
4数组分块 chunk([1,2,3,4,5], 2 → [[1,2],[3,4],[5]])

场景:分页、批量请求。把大数组切成 size 个一组。lodash 的 chunk 实现。

// 方法1:slice + for(最直观) function chunk(arr, size) { const result = [] for (let i = 0; i < arr.length; i += size) { result.push(arr.slice(i, i + size)) } return result } chunk([1, 2, 3, 4, 5], 2) // [[1,2],[3,4],[5]] // 方法2:reduce(函数式) const chunk2 = (arr, size) => arr.reduce((acc, _, i) => i % size === 0 ? [...acc, arr.slice(i, i + size)] : acc , []) // 方法3:splice(会修改原数组,注意) function chunk3(arr, size) { const result = [] const copy = [...arr] // 复制避免修改原数组 while (copy.length) result.push(copy.splice(0, size)) return result }
面试推荐:方法1,思路清晰。slice(i, i+size) 切片是关键。
5数组分组 groupBy(按字段分组)

场景:按部门分组员工、按状态分组订单。等同于 lodash 的 groupBy。

function groupBy(arr, fn) { // fn 可以是属性名 'dept' 或函数 item => item.dept return arr.reduce((acc, item) => { const key = typeof fn === 'function' ? fn(item) : item[fn] ;(acc[key] ||= []).push(item) return acc }, {}) } const users = [ { name: 'Tom', dept: '技术' }, { name: 'Jerry', dept: '产品' }, { name: 'Lucy', dept: '技术' } ] groupBy(users, 'dept') // { '技术': [{Tom...},{Lucy...}], '产品': [{Jerry...}] } // 进阶:按计算字段分组(如按年龄段) groupBy(users, u => u.age < 18 ? '未成年' : '成年')
关键(acc[key] ||= []).push(item) 是分组的标准技巧,key 不存在时自动建空数组。

进阶必会

6手写数组 sort(自定义比较 + 多字段排序)

场景:列表按多个字段排序(先按状态再按时间),面试常考。

// 单字段排序(原生 sort 的用法) const arr = [3, 1, 4, 1, 5] arr.sort((a, b) => a - b) // 升序 [1,1,3,4,5] arr.sort((a, b) => b - a) // 降序 // 多字段排序:先按 status 升序,status 相同按 time 降序 function multiSort(arr) { return arr.sort((a, b) => { if (a.status !== b.status) return a.status - b.status // 第一关键字 return new Date(b.time) - new Date(a.time) // 第二关键字(注意降序 b-a) }) } // 中文按拼音排序(业务场景:城市名排序) ['北京', '上海', '广州', '深圳'].sort(new Intl.Collator('zh-CN').compare) // ['北京','广州','上海','深圳'] // 通用 sortBy 封装(lodash 风格) const sortBy = (arr, key, order = 'asc') => [...arr].sort((a, b) => { const r = a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0 return order === 'asc' ? r : -r })
:默认 sort 按字符串排序,[10, 2, 1].sort() 得到 [1, 10, 2]!必须传比较函数。
7两数之和(返回索引,LeetCode 第一题)

场景:nums = [2,7,11,15], target = 9,返回 [0,1]。哈希表经典题。

// 方法1:哈希表 O(n)(推荐) function twoSum(nums, target) { const map = new Map() // 值 → 索引 for (let i = 0; i < nums.length; i++) { const need = target - nums[i] if (map.has(need)) return [map.get(need), i] map.set(nums[i], i) } return [] } // 方法2:双层 for(暴力 O(n²),仅作对照) function twoSum2(nums, target) { for (let i = 0; i < nums.length; i++) { for (let j = i + 1; j < nums.length; j++) { if (nums[i] + nums[j] === target) return [i, j] } } }
核心:用空间换时间。把"找 need"从 O(n) 降到 O(1)。面试必答哈希表版。
8最大子数组和(连续子数组,Kadane 算法)

场景:[-2,1,-3,4,-1,2,1,-5,4] 最大连续子数组是 [4,-1,2,1],和为 6。动态规划入门题。

// Kadane 算法:O(n) 一遍过 function maxSubArray(nums) { let curSum = nums[0] // 以当前元素结尾的最大和 let maxSum = nums[0] // 全局最大和 for (let i = 1; i < nums.length; i++) { // 要么接着前面的和,要么从当前重新开始(取较大者) curSum = Math.max(nums[i], curSum + nums[i]) maxSum = Math.max(maxSum, curSum) } return maxSum } maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]) // 6 // 思路解析(动态规划): // dp[i] = max(nums[i], dp[i-1] + nums[i]) // 如果前面的累积和是负数,不如扔掉从当前重新开始
状态转移curSum = Math.max(nums[i], curSum + nums[i]) 是核心。面试要讲清楚"为什么前面的负和要丢弃"。
9数组交错合并(zip / 拉链合并)

场景:把两个数组合并成 [[a1,b1],[a2,b2]...],类似 Python 的 zip。表单批量生成、双列表对齐。

// 基础版:两个数组 zip const zip = (a, b) => Math.min(a.length, b.length) >= 0 ? Array.from({ length: Math.min(a.length, b.length) }, (_, i) => [a[i], b[i]]) : [] zip([1, 2, 3], ['a', 'b', 'c']) // [[1,'a'],[2,'b'],[3,'c']] // 通用版:N 个数组 zip const zipAll = (...arrs) => { const len = Math.min(...arrs.map(a => a.length)) return Array.from({ length: len }, (_, i) => arrs.map(a => a[i])) } zipAll([1, 2], [3, 4], [5, 6]) // [[1,3,5],[2,4,6]] // 反操作 unzip const unzip = arr => arr[0].map((_, i) => arr.map(row => row[i]))
应用:表头和数据行合并成对象数组,zip(headers, row).map(([k,v]) => ({[k]:v}))
10比较两个数组差异(找出增删改)

场景:表单提交前对比新旧数据,找出哪些项被新增/删除/修改。Vue/React 项目高频。

function diffArrays(oldArr, newArr, key = 'id') { const oldMap = new Map(oldArr.map(item => [item[key], item])) const newMap = new Map(newArr.map(item => [item[key], item])) const added = newArr.filter(item => !oldMap.has(item[key])) // 新增的 const removed = oldArr.filter(item => !newMap.has(item[key])) // 删除的 const changed = newArr.filter(item => { // 修改的 const old = oldMap.get(item[key]) return old && JSON.stringify(old) !== JSON.stringify(item) }) return { added, removed, changed } } const oldList = [{ id: 1, name: 'Tom' }, { id: 2, name: 'Jerry' }] const newList = [{ id: 1, name: 'Tom2' }, { id: 3, name: 'Lucy' }] diffArrays(oldList, newList) // { added: [{id:3,name:'Lucy'}], removed: [{id:2,name:'Jerry'}], changed: [{id:1,name:'Tom2'}] }
对象比较注意:用 JSON.stringify 比较简单但有键顺序问题({a:1,b:2} 和 {b:2,a:1} 字符串不同但内容相同)。严谨做法是逐字段比较或用 lodash.isEqual。
🎯 数组题复习重点:① Set 去重(秒答)② reduce 扁平化(手写)③ Map 对象去重/分组 ④ 滑动窗口/双指针(高频)⑤ reduce 是数组题的瑞士军刀,几乎所有数组转换都能用。