4個常見的算法問題,前端開發(fā)者必須要了解一下

英文 | https://medium.com/frontend-canteen/algorithms-for-front-end-interviews-4aa329bb2ce4
翻譯 | 楊小愛
快速排序本身被廣泛使用。 JavaScript 中 Array 的 sort 方法是通過 V8 引擎中的快速排序?qū)崿F(xiàn)的。
選擇數(shù)組中的一個元素作為“樞軸”。我們可以選擇任何元素作為樞軸,但中間的元素更直觀。 所有小于樞軸的元素都被移動到樞軸的左側(cè);所有大于或等于樞軸的元素都被移動到樞軸的右側(cè)。 對于樞軸左右的兩個子集,重復第一步和第二步,直到所有子集中只剩下一個元素。
let arr = [86, 24, 64, 48, 15, 30, 90, 49]
執(zhí)行
首先,定義一個參數(shù)為數(shù)組的快速排序函數(shù)。
var quickSort = function(arr) {};
然后,檢查數(shù)組中的元素個數(shù),如果小于等于 1,則返回。
var quickSort = function(arr) {if (arr.length <= 1) { return arr; }};
接下來,選擇樞軸,將其與原始數(shù)組分開,并定義兩個空數(shù)組來存儲兩個子集。
var quickSort = function(arr) {if (arr.length <= 1) { return arr; }var pivotIndex = Math.floor(arr.length / 2) ;var pivot = arr.splice(pivotIndex, 1)[0];var left = [];var right = [];};
然后,開始遍歷數(shù)組,小于主元的元素放入左子集中,大于主元的元素放入右子集中。
var quickSort = function(arr) {if (arr.length <= 1) { return arr; }var pivotIndex = Math.floor(arr.length / 2) ;var pivot = arr.splice(pivotIndex, 1)[0];var left = [];var right = [];for (var i = 0; i < arr.length; i++){if (arr[i] < pivot) {left.push(arr[i]);} else {right.push(arr[i]);}}};
最后,通過使用遞歸重復這個過程,得到排序后的數(shù)組。
var quickSort = function(arr) {if (arr.length <= 1) { return arr; }var pivotIndex = Math.floor(arr.length / 2);var pivot = arr.splice(pivotIndex, 1)[0];var left = [];var right = [];for (var i = 0; i < arr.length; i++){if (arr[i] < pivot) {left.push(arr[i]);} else {right.push(arr[i]);}}return quickSort(left).concat([pivot], quickSort(right));};
用法:

以整個數(shù)組的中間元素作為搜索鍵開始。 如果搜索鍵的值等于項目,則返回搜索鍵的索引。 或者搜索鍵的值小于區(qū)間中間的項,則將區(qū)間縮小到下半部分。 否則,將其縮小到上半部分。 從第二點開始反復檢查,直到找到值或區(qū)間為空。
[15, 24, 30, 48, 49, 64, 86, 90, 100, 121, 130]如果我們要檢查這個數(shù)組中是否存在 48:

執(zhí)行
function binarySearch(arr, x) {// left index of the current intervallet l = 0;// right index of the current intervallet r = arr.length - 1;// middle index of the current intervallet mid;while (r >= l) {mid = l + Math.floor((r - l) / 2);// If the element is present at the middle// itselfif (arr[mid] == x) {return mid;}// If element is smaller than mid, then// it can only be present in left subarrayif (arr[mid] > x) {r = mid - 1;}// Else the element can only be present// in right subarrayif (arr[mid] < x) {l = mid + 1;}}// We reach here when element is not// present in arrayreturn -1;}
用法:

比較
二分搜索比正常的線性搜索更快。

但是,你只能在排序數(shù)組上使用二進制搜索!
3、如何反轉(zhuǎn)單鏈表?
鏈表是表示一系列節(jié)點的數(shù)據(jù)結(jié)構(gòu),其中每個節(jié)點包含兩條信息:節(jié)點的值和指向列表中下一個節(jié)點的指針/引用。鏈表的開頭稱為頭,鏈表末尾的節(jié)點稱為尾,指向空值;null。

與數(shù)組相比,鏈表的主要好處是更容易在列表中插入或刪除節(jié)點。另一方面,不允許隨機訪問數(shù)據(jù),因為與數(shù)組不同,鏈表沒有索引。
鏈表也廣泛用于前端項目。例如,React 的 Fiber 使用鏈表。
我們可以這樣創(chuàng)建一個鏈表:
function Node(value) {this.value = valuethis.next = null}let head = new Node(1)head.next = new Node(3)head.next.next = new Node(9)head.next.next.next = new Node(6)head.next.next.next.next = new Node(2)
如果我們被要求反轉(zhuǎn)一個鏈表,我們需要讓尾部成為頭部:

我們可以迭代或遞歸地反轉(zhuǎn)鏈表,但我們將只關注通過以下步驟來解釋今天的迭代方法:
1)、初始化三個指針:prev、current 和 next:
prev:此指針將跟蹤當前節(jié)點之前的節(jié)點,我們將其設置為空,因為單鏈表節(jié)點沒有對其前一個節(jié)點的引用。
current:這個將從列表的頭部開始,并跟蹤我們當前所在的節(jié)點。
next:此指針將在其引用更改之前存儲下一個節(jié)點,并且最初設置為 null。
2)、遍歷所有節(jié)點,遍歷鏈表,只要有節(jié)點,每次迭代執(zhí)行以下操作:
設置為 current.next 的 next (我們需要在更改之前存儲 current 的下一個節(jié)點)。
將 current.next 設置為 prev(我們現(xiàn)在可以通過反轉(zhuǎn)鏈接來更改當前的下一個)。
將 prev 設置為 current(此步驟將前一個節(jié)點向前移動)。
設置當前等于下一個(這一步將當前節(jié)點向前移動)。
對所有節(jié)點重復步驟 2。
3)、 返回 prev 指針作為反向列表的新頭。
const reverseList = head => {let prev = nulllet next = nulllet current = headwhile(current !== null){next = current.nextcurrent.next = prevprev = currentcurrent = next}return prev}
用圖解釋:

前端開發(fā)經(jīng)常需要解析模板語法,所以面試中經(jīng)常會問到下面這個問題。
描述:
給定一個僅包含字符 '(', ')', '{', '}', '[' 和 ']' 的字符串 s,確定輸入字符串是否有效。
輸入字符串在以下情況下有效:
括號必須用相同類型的括號閉合。
括號必須以正確的順序閉合。
?示例 1:
Input: s = "()"Output: true
示例2:
Input: s = "()[]{}"Output: true
示例3:
Input: s = "(]"Output: false
示例4:
Input: s = "([)]"Output: false
?示例5:
Input: s = "{[]}"Output: true
約束:
1 <= s.length <= 104
s 僅包含括號 '()[]{}'。
分析:
對于這類問題,我們一般更喜歡使用棧數(shù)據(jù)結(jié)構(gòu)。為什么可以用堆棧來完成?
想想有效的括號是什么意思?是對稱的意思。
根據(jù)棧的后進先出原則,數(shù)據(jù)的入棧和出棧順序是對稱的。比如1、2、3、4、5、6依次入棧,對應的出棧順序為6、5、4、3、2、1:
123456654321
因此,你可以在這里記住一個規(guī)則:如果問題涉及括號或其他對稱結(jié)構(gòu),則相關解決方案很可能與堆棧有關。
我們的想法是:遍歷整個字符串:
如果找到左括號,則將其添加到堆棧中。
如果找到右括號,則彈出堆棧頂部的一個元素,并確定當前的右括號是否匹配它。
對于有效的括號,整個流程可能如下所示:

執(zhí)行:
const isValid = function(s) {if (!s) {return true;}// array can be used as a stackconst stack = [];const len = s.length;for (let i = 0; i < len; i++) {// cacheconst ch = s[i];if (ch === "(" || ch === "{" || ch === "[") {stack.push(leftToRight[ch]);}else {// If the stack is not empty and the// openning parenthesis at the top of the stack does not// match the current character, it is invalid.if (!stack.length || stack.pop() !== ch) {return false;}}}// If all parentheses can be matched successfully,// then the final stack should be emptyreturn !stack.length;};

總結(jié)
以上就是我分享的4個常見的算法問題。當然,這些內(nèi)容還遠遠不夠,但是由于文章篇幅關系,這次就不繼續(xù)了。
很多初級前端開發(fā)者,尤其是自學成才的(比如我),面對面試可能會有點害怕,尤其是在我們不擅長的算法領域。
在面試前多練習算法題,為自己搭建知識庫。提前準備有助于增強自信心。
面試的時候,積極思考自己的知識庫和面試題之間的關系,然后多說自己擅長什么,即使內(nèi)容和題本身關系不大。
面試結(jié)束后,主動與面試官溝通,問他問題的邏輯。同時,你可以把面試中不懂的問題記錄下來,然后仔細研究。畢竟,這不會是你的最后一次面試。
今天的內(nèi)容就先到這里了,感謝你的閱讀。
學習更多技能
請點擊下方公眾號
![]()

