數組是一種線性表數據結構,它用一組連續的內存空間,來存儲一組具備相同類型的數據。
數組能夠根據索引下標隨機訪問(時間複雜度爲 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,不妨加入前端食堂,一塊兒並肩做戰,刷個痛快。