数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具备相同类型的数据。
数组能够根据索引下标随机访问(时间复杂度为 O(1)),这个索引一般来讲是数字,用来计算元素之间的存储位置的偏移量。javascript
与其余编程语言不一样,JavaScript 中的数组长度能够随时改变,数组中的每一个槽位能够储存任意类型的数据,而且其数据在内存中也能够不连续。
前端
上文提到,这个索引一般是数字,也就是说在 JavaScript 中,经过字符串也能够访问对应的元素:java
const arr = [0, 1, 2] arr['1'] // 1
其实,JavaScript 中的数组是一种比较特殊的对象,由于在 JavaScript 中,对象的属性名必须是字符串,这些数字索引就被转化成了字符串类型。git
// 1. 使用 Array 构造函数 let webCanteen = new Array() // 初始为 20 的数组 let webCanteen = new Array(20) // 传入要保存的元素 let webCanteen = new Array('食堂老板', '店小二', '大厨') // 若是传入了非数值,则会建立一个只包含该特定值的数组 let webCanteen = new Array('前端食堂') // 省略 new 操做符 let webCanteen = Array(20) // 2. 使用数组字面量 let webCanteen = ['食堂老板', '店小二', '大厨'] let webCanteen = []
ES6 新增了 2 个用于建立数组的静态方法:Array.of
、Array.from
。github
Array.of
用于将一组参数转换为数组实例(不考虑参数数量和类型),而 Array.from
用于将类数组结构和可遍历对象转换为数组实例(浅拷贝)。web
// Array.of 和 Array 构造函数之间的区别在于处理整数参数 Array(5) // [, , , , ,] Array(1, 2, 3) // [1, 2, 3]
// Array.from 拥有 3 个参数 // 1. 类数组对象,必选 // 2. 加工函数,新数组中的每一个元素会执行该回调,可选 // 3. this,表示加工函数执行时的 this,可选 const obj = {0: 10, 1: 20, 2: 30, length: 3} Array.from(obj, function(item) { return item * 2 }, obj) // [20, 40, 60] // 不指定 this 的话,加工函数能够是一个箭头函数 Array.from(obj, (item) => item * 2)
let arr = [] // 1. instanceof arr instanceof Array // 2. constructor arr.constructor === Array // 3. Object.prototype.isPrototypeOf Array.prototype.isPrototypeOf(arr) // 4. getPrototypeOf Object.getPrototypeOf(arr) === Array.prototype // 5. Object.prototype.toString Object.prototype.toString.call(arr) === '[object Array]' // 6. Array.isArray ES6 新增 Array.isArray(arr)
前 4 种方法比较渣,丝绝不负责任,好比咱们将 arr 的 __proto__
指向了 Array.prototype
后:面试
let arr = { __proto__: Array.prototype } // 1. instanceof arr instanceof Array // true // 2. constructor arr.constructor === Array // true // 3. Object.prototype.isPrototypeOf Array.prototype.isPrototypeOf(arr) // true // 4. getPrototypeOf Object.getPrototypeOf(arr) === Array.prototype // true
建立数组:Array.of、Array.from
算法
改变自身(9 种):pop
、push
、shift
、unshift
、reverse
、sort
、splice
、copyWithin
、fill
编程
不改变自身(12 种):concat
、join
、slice
、toString
、toLocaleString
、valueOf
、indexOf
、lastIndexOf
、未造成标准的 toSource
,以及 ES7 新增的方法 includes
,以及 ES10 新增的方法 flat
、flatMap
数组
不会改变自身的遍历方法一共有(12 种): forEach
、every
、some
、filter
、map
、reduce
、reduceRight
,以及 ES6 新增的方法 find
、findIndex
、keys
、values
、entries
。
本文就不给你们一一去介绍这些 API 的用法了,目的是为你们起个头,若是你对数组的 API 尚未烂熟于心的话,能够找个整块的时间统一的进行系统学习。
由于这些 API 正是咱们平常开发中的武器库,完全搞清楚它们会大大提升开发效率,避免在开发中频繁的查阅文档。
我按照如上规律整理了一张表格,方便你总结。
改变自身 | 不改变自身 | 遍历方法(不改变自身) | |
---|---|---|---|
ES5及之前 | pop、push、shift、unshift、reverse、sort、splice | concat、join、slice、toString、toLocaleString、valueOf、indexOf、lastIndexOf | forEach、every、some、filter、map、reduce、reduceRight |
ES6+ | copyWithin、fill | includes、flat、flatMap、toSource | find、findIndex、keys、values、entries |
其实数组中的方法有一些共同之处,咱们能够将其整理出来,更加方便咱们理解和记忆。
push、unshift
都返回数组新的长度。pop、shift、splice
都返回删除的元素,或者返回删除的多个元素组成的数组。forEach、every、some、filter、map、find、findIndex
,它们都包含 function(value, index, array) {}
和 thisArg
这样两个形参。定型数组(typed array)是 ECMAScript 新增的结构,目的是提高向原生库传输数据的效率。这部分的内容本文再也不展开,有兴趣的同窗们能够自行学习。
回顾了 JavaScript 中数组的基本知识后,立刻开启咱们愉快的刷题之旅,我整理了 6 道高频的 LeetCode 数组题的题解以下。
符合第一直觉的暴力法,潜意识里要学会将两数之和
转换为两数之差
。
而后问题就变得像切菜同样简单了,双层循环找出当前数组中符合条件的两个元素,并将它们的索引下标组合成数组返回即所求。
const twoSum = function(nums, target) { for (let i = 0; i < nums.length; i++) { for (let j = i + 1; j < nums.length; j++) { if (nums[i] === target - nums[j]) { return [i, j] } } } }
写出这种方法是不会让面试官满意的,因此接下来咱们要想办法进行优化。
算法优化的核心方针基本上都是用空间换时间。
咱们能够借用 Map 存储遍历过的元素及其索引,每遍历一个元素时,去 Map 中查看是否存在知足要求的元素。
若是存在的话将其对应的索引从 Map 中取出和当前索引值组合为数组
返回即为所求,若是不存在则将当前值做为键,当前索引做为值
存入。
题目要求返回的是数组下标,因此 Map 中的键名是数组元素,键值是索引。
const twoSum = function(nums, target) { const map = new Map() for (let i = 0; i < nums.length; i++) { const diff = target - nums[i] if (map.has(diff)) { return [map.get(diff), i] } map.set(nums[i], i) } }
虽然是中等难度,但这道题解起来仍是比较简单的,老规矩,咱们看下符合第一直觉的暴力法:
幼儿园数学题:矩形面积 = 长 * 宽
放到咱们这道题中,矩形的长和宽就分别对应:
双重 for 循环遍历全部可能,记录最大值。
const maxArea = function(height) { let max = 0 // 最大容纳水量 for (let i = 0; i < height.length; i++) { for (let j = i + 1; j < height.length; j++) { // 当前容纳水量 let cur = (j - i) * Math.min(height[i], height[j]) if (cur > max) { max = cur } } } return max }
暴力法时间复杂度 O(n^2) 过高了,咱们仍是要想办法进行优化。
咱们能够借用双指针来减小搜索空间,转换为双指针的视角后,回顾矩形的面积对应关系以下:
(矩形面积)容纳的水量 = (两条垂直线的距离)指针之间的距离 * (两个指针指向的数字中的较小值)两条垂直线中较短的一条的长度
设置两个指针,分别指向头和尾(i指向头,j指向尾),不断向中间逼近,在逼近的过程当中为了找到更长的垂直线:
有点贪心思想那味儿了,由于更长的垂直线能组成更大的面积,因此咱们放弃了较短的那一条的可能性。
可是这么作,咱们有没有更能漏掉一个更大的面积的可能性呢?
先告诉你答案是不会漏掉。
关于该算法的正确性证实已经有不少同窗们给出了答案,感兴趣的请戳下面连接。
const maxArea = function(height) { let max = 0 // 最大容纳水量 let left = 0 // 左指针 let right = height.length - 1 // 右指针 while (left < right) { // 当前容纳水量 let cur = (right - left) * Math.min(height[left], height[right]); max = Math.max(cur, max) height[left] < height[right] ? left ++ : right -- } return max };
先明确,题目不只是要求 a + b + c = 0,并且须要三个元素都不重复。
因此咱们能够先将数组从小到大排序,排序后,去除重复项会更加简单。
const threeSum = function(nums) { const result = [] const len = nums.length if (len < 3) { return result } nums.sort((a, b) => a - b) for (let i = 0; i < len - 2; i++) { if (nums[i] > 0) { break } if (i > 0 && nums[i] === nums[i - 1]) { continue } let L = i + 1 let R = len - 1 while (L < R) { let sum = nums[i] + nums[L] + nums[R] if (sum === 0) { result.push([nums[i], nums[L], nums[R]]) while(nums[L] === nums[L + 1]){ L++ } while(nums[R] === nums[R - 1]){ R-- } L++ R-- } else if (sum < 0) { L++ } else { R-- } } } return result }
题目要求原地删除重复出现的元素,不要使用额外的数组空间,返回移除后数组的新长度。
先明确,这道题给咱们提供的是排好序的数组,因此重复的元素必然相邻。
因此实际上咱们只须要将不重复的元素移到数组的左侧,并返回其对应的长度便可。
const removeDuplicates = function(nums) { if (nums.length === 0) return 0 let i = 0 const n = nums.length for (let j = 1; j < n; j++) { if (nums[j] != nums[j - 1]) { i++ nums[i] = nums[j] } } return i + 1 };
加一,其实就是小学数学题,很简单,咱们逐步来分析。
数字 9 加 1 会进位,其余的数字不会。
因此,状况无非下面这几种:
const plusOne = function(digits) { for (let i = digits.length - 1; i >= 0; i--) { if (digits[i] === 9) { digits[i] = 0 } else { digits[i]++ return digits } } digits.unshift(1) return digits };
题目要求将全部 0 移动到数组的末尾,同时还要保持非零元素的相对顺序。
在此基础上附加了两个条件:
咱们能够借助双指针来进行求解,求解过程以下:
const moveZeroes = function (nums) { let i = 0, j = 0; while (i < nums.length) { if (nums[i] != 0) { [nums[i], nums[j]] = [nums[j], nums[I]]; i++; j++; } else { i++; } } }
年初立了一个 flag,上面这个仓库在 2021 年写满 100 道前端面试高频题解,目前进度已经完成了 50%。
若是你也准备刷或者正在刷 LeetCode,不妨加入前端食堂,一块儿并肩做战,刷个痛快。