不學不知道,一學嚇一跳。能夠利用數據結構的思想來實現一些算法,能把本來O(n^2)的時間複雜度下降到O(1),雖然只是對一些數組的api進行封裝javascript
常常聽不少前輩說,編程語言是相通的,掌握了一門,其餘語言就就很容易掌握,可是我的認爲每門語言都有本身的優缺點,都有本身能勝任的地方,也都有本身無能爲力的地方。 好比說讓咱們前端工程師踏入後端領域的node.js,也只是在不少公司做爲中間層來使用,並不能像java,c那樣來真正的代替後端。 好比說機器語言python,這樣近乎萬能的語言,在面對高性能計算的時候,利用的不少python庫,底層也都是c語言實現的。 因此我的認爲真正相通的不是語言,而是數據結構和算法。 數據結構和算法是脫離編程語言而存在的,不一樣的語言有不一樣的實現,但內在的邏輯不會有變化,所體現的編程思想不會有變化。html
在以前的面試中,去了一家是線上課程的公司,當時筆試和前兩面都比較順利,到了終面部門負責人的時候,懵圈了。那位大佬是計算機出身的前百度高級工程師,特別注重數據結構和算法。寒暄了幾句後開始進入了主題,(噩夢的開始)看了我寫的筆試題後,問我爲何這個快排這樣寫?我當時寫的阮一峯老師的版本, 我沒說話,而後又問我,你不知道這樣寫不只時間複雜度會加大,連空間複雜度都會消耗嗎?我懵圈的搖了搖頭,而後又問我,你知道堆排序嗎?我又懵圈的搖了搖頭,而後又問我,你知道時間複雜度嗎?我又無奈的搖了搖頭;而後大佬放棄了,再也不問了,開始了對個人評價,你連這最基本的數據結構都不知道,怎麼能知道你的代碼是好是壞呢,若是遇到bug別人半個小時能解決的,你說不定得用兩個小時。。。深入教育了一番出去和hr談話了,最後的結果雖然是拿到offer了,可是級別定的低,money也給的少。 我一直覺得前端和數據結構和算法無關,能實現業務功能就行,可是通過此次面試後,我打破了以前的觀點,就算寫業務,也有寫的好也有寫的通常的,以前的嵌套循環,每一層都把時間複雜度提升了一個檔次,因此決定重頭學起數據結構和算法。前端
數據結構的精髓在於總結提煉了許多存儲管理和使用數據的模式,這些模式的背後是最精華的編程思想,這些思想的領悟須要時間,不要想固然的認爲學會了幾種數據結構就能夠在工做中大顯身手,但學會了數據結構,對自身能力的提高是不言而喻的。java
接下來開始主題吧node
棧是一種特殊的線性表,僅能在線性表的一端操做,棧頂容許操做,棧底不容許操做。棧的特性:先進後出(後進先出)。python
下圖展現了棧的工做原理 es6
棧某種意義上講,它像是一個開口的盒子,先放進去的東西老是會被後放進去的東西壓在下面,那麼若是想拿出被壓住的東西,必需要先取出頂部的東西,也就是後放進去的東西。面試
就像咱們平常生活中的羽毛球桶 算法
從數據存儲的角度看,實現棧有兩種方式,一種是以數組作基礎,一種是以鏈表作基礎,數組是最簡單的實現方式,本文以基礎的數組來實現棧。編程
棧的基本操做包括建立棧、銷燬棧、出棧、入棧、獲取棧頂元素、獲取棧的大小、清空棧。
咱們定義如下幾個棧的方法:
- push 添加一個元素到棧頂(向桶裏放入一個羽毛球)
- pop 彈出棧頂元素(從桶裏頂部拿出一個羽毛球)
- top 返回棧頂元素(看一眼桶裏最頂端的羽毛球,可是不拿出來)
- isEmpty 判斷棧是否爲空(看看羽毛球是否是都用完了)
- size 返回棧裏元素的個數(數一下桶裏還有多少羽毛球)
- clear 清空棧(把桶裏的羽毛球都倒出來扔掉)
而後咱們利用es6的class的實現以上的方法 新建一個stack.js文件
class Stack {
constructor() {
this.items = []; // 使用數組存儲數據
}
push(item) {
this.items.push(item); // 往棧裏壓入一個元素
}
pop() {
return this.items.pop(); // 把棧頂的元素移除
}
top() {
return this.items[this.items.length - 1]; // 返回棧頂的元素
}
isEmpty() {
return this.items.length === 0; //返回棧是否爲空
}
size() {
return this.items.length; // 返回棧的大小
}
clear() {
this.items = []; // 清空棧
}
}
複製代碼
看完上面的代碼,是否是以爲很驚訝,這裏實現的棧,居然就只是對數組作了一層封裝而已!
只是作了一層封裝麼?
- 給你一個數組,你能夠經過索引操做任意一個元素,可是給你一個棧,你能操做任意元素麼?棧提供的方法只容許你操做棧頂的元素,也就是數組的最後一個元素,這種限制其實提供給咱們一種思考問題的方式,這個方式也就是棧的特性,後進先出。
- 既然棧的底層實現其實就是數組,棧能作的事情,數組同樣能夠作啊,爲何弄出一個棧來,是否是畫蛇添足?封裝是爲了更好的利用,站在棧的肩膀上思考問題顯然要比站在數組的肩膀上思考問題更方便,後面的練習題你將有所體會。
3.1.1 判斷括號是否匹配 說說我以前遇到的面試題,給一段字符串,判斷裏面的括號是不是成對出現 好比說
()ss()ss(sss(ss)(ss)ss) 合法
()ss()ss(sss(ss)(ss)ss)) 不合法
3.1.2 思路分析 括號有嵌套關係,也有並列關係,若是咱們用數組或者對象的方法也能解決,今天咱們試着用棧來解決這個問題。
- 遍歷字符串
- 若是是左括號,就壓入棧中
- 若是是右括號,判斷棧是否爲空,若是不爲空,則把棧頂元素移除(也就是在棧中存放的左括號),這對括號就抵消了;若是不爲空,就說明缺乏左括號,返回false
- 循環結束後,看棧的大小是否爲0,若是不爲0,就說明沒有成對出現,爲0,就說明所有抵消了。
3.1.3 用棧來分析是否是以爲很簡單呢,下面看代碼實現
{
function isDouuble(str) {
const stack = new Stack();
const len = str.length;
for (let i = 0; i < len; i++) {
const item = str[i];
if (str[i] === "(") {
stack.push(item); // 入棧
} else if (item === ")") {
if (stack.isEmpty()) {
return false;
} else {
stack.pop(); // 出棧
}
}
}
return stack.size() === 0;
}
console.log(isDouuble("()ss()ss(sss(ss)(ss)ss)")); // true
console.log(isDouuble("()ss()ss(sss(ss)(ss)ss)(")); // false
console.log(isDouuble("()ss()ss(sss(ss)(ss)ss))")); // false
console.log(isDouuble("()ss()ss(sss(ss)(ss)ss))(")); // false
}
複製代碼
3.2.1 實現一個min方法的棧
實現一個棧,除了常見的push,pop方法之外,提供一個min方法,返回棧裏最小的元素,且時間複雜度爲o(1)
3.2.2 思路分析 能夠利用兩個棧來實現,一個棧用來存儲數據,一個棧用來存儲棧裏最小的數據; 利用編程中分而治之的思想,就是分開想分開處理
- 定義兩個棧,dataStack 和 minStack;
- 對於dataStack棧來講,正常的psuh,pop實現就好;
- 對於minStatck棧來講,它是要存儲棧裏最小的值,因此當minStack爲空的時候,那麼push進來的數據就是最小的;若是不爲空,此時minStack棧頂的元素就是最小的,若是push進來的元素比棧頂的元素還小,直接push進來就行,這樣minStack棧的棧頂始終都是棧裏的最小值。
3.2.3 代碼實現 (時間複雜度爲O(1))
{
class MinStack {
constructor() {
this.dataStack = new Stack(); // 普通的棧
this.minStack = new Stack(); // 存儲最小值的棧
}
// push 和 pop 兩個棧都要操做,保持大小統一
push(item) {
this.dataStack.push(item); // 常規操做
if (this.minStack.isEmpty() || item < this.minStack.top()) {
this.minStack.push(item); // 保證minStack棧頂是最小的值
} else {
this.minStack.push(this.minStack.top()); // 保持兩個棧的大小同樣
}
}
pop() {
this.minStack.pop();
return this.dataStack.pop(); // 返回真實的數字
}
min() {
return this.minStack.top(); // 返回最小的數字
}
}
const minstack = new MinStack();
minstack.push(3);
minstack.push(2);
minstack.push(6);
minstack.push(8);
console.log(minstack.min()); // 2
console.log(minstack.pop()); // 8
minstack.push(1);
console.log(minstack.min()); // 1
}
複製代碼
棧的底層是否是使用了數組這不重要,重要的是棧的這種後進先出的特性,重要的是咱們只能操做棧頂元素的的限制,必定要忽略掉棧的底層如何實現,而只去關心棧的特性。