面試中遇到這道算法題,你能答對嗎?(送10元現金紅包)

有許多讀者在後臺給我留言,說自己即將面臨畢業或者換工作,希望可以多爲他們分享一些面試相關知識。

其實,大多數公司在面試時都尤爲看中候選人的算法能力,他們甚至會讓候選人當場寫代碼,我認識一位Stony Brook University的朋友,應聘亞馬遜,上來就是3道LeetCode題。公司爲什麼喜歡先來算法題並現場寫代碼呢?因爲算法能力也會直接反映出一個程序員水平的高低。

今天,就用一道經典面試題爲例,給大家分享前 Facebook 面試官的解題思路,哪怕你暫時不需要面試,也可以通過培養算法思維,提升工作效率。

LeetCode 第 20 題:判斷括號是否合法 https://leetcode-cn.com/problems/valid-parentheses/description/

2

你能5分鐘內準確無誤地寫出這道題的代碼嗎? 不管有沒有把握,都可以看下面的詳細分析,帶有清晰的示意圖。最爲關鍵的是,本題至少有兩種解法,只需要不到 10 行的代碼,不妨仔細讀一下!

我們先來看題幹:給定一個字符串,這個字符串裏面只包含大中小括號,讓你判斷這個字符串是否合法,所謂的括號合法指的就是左括號和右括號必須匹配,同時也允許嵌套的情況。

具體規則如下:左括號必須用相同類型的右括號閉合。左括號必須以正確的順序閉合。如果覺得規則比較抽象,那我們就來看幾個簡單的例子:

"()" "()[]" "([)]" "((([]))" "]][["

第一種情況 "()" 比較簡單,就是左小括號和右小括號,顯然可以配對,所以是合法的。

第二種情況 "()[]" 就是多了一對中括號,所以也是合法的。

我們再看第三種情況 "([)]" ,在第二個例子的基礎上,這裏把中間的兩個括號調換了一下位置,這樣一來,左中括號和右小括號就無法進行匹配的,即左小括號和左中括號都沒有以正確的順序閉合,顯然就不合法了。

第四種情況是一組嵌套括號 "((([]))",最左邊是 3 個左小括號,最右邊有 2 個右小括號,中間則是一對相匹配中括號,由於最右邊的右小括號只有兩個,無法和左邊的 3 個一一匹配,所這個也是不合法的。但是如果把最右邊的小括號增加一個,變成"((([])))",這樣就合法了。

第五種情況 "]][[" ,一開始出現的就是一個右括號,沒有左括號與之匹配,顯然不合法。

那麼這個題目應該怎麼做呢?

這是一個比較經典的面試題,用到的也是比較經典的數據結構,就是用“棧”來解決這個問題,我們來看看具體該怎麼做。

首先,我們把棧畫出來是下面這個樣子的:

然後我們按照從左到右的順序依次把字符串中的括號放到這個棧裏面。

如果第一個進來的是左括號,對於這種情況,我們現在沒法判斷它是否合法,還需要後續看有沒有相應的右括號和它匹配。那我們就先把它留着,留着的意思就是把左括號先放在棧底去存着的,也就是壓入棧(push)。

如果是右括號先進來呢?因爲右括號不能獨立存在的,如果一上來就直接來個右括號,那顯然就不合法了。所以右括號要進來的時候,要查看(peek)當前棧頂是否有相應的左括號與之相匹配。如果是右小括號,那就看一下棧頂是否有左小括號,如果是右中括號,就看一下棧頂是否有左中括號。

如果當前棧頂有與之相匹配的左括號的話,那就要把這個棧頂元素給推(pop)出去了,如果不匹配的話,就直接返回不匹配。

最後還有一點要注意的就是,這個流程全部走完一遍後,如果字符串裏面的括號是一一匹配的,那麼最後這個棧本身應該是空的。我們可以用之前的 5 個例子來驗證一下。

首先第一個字符串"()",左小括號"("進來之後先把它壓入到棧底。

然後要進來的是右小括號")",此時我們就要查看(peek)當前棧頂的元素是不是和這個右小括號相匹配的,這種情況下剛好是匹配的,那麼就把這個元素"("從棧裏面推出(pop)。

然後這個字符串的全部流程就走完了,現在我們可以看到這個棧爲空,這就說明這個字符串裏面的括號都是相匹配的,大家都很幸福的配對在一起了,當然也就說明是合法的。

第二個情況"()[]"也類似,只不過多重複了一對中括號的匹配流程,就不再贅述了。

來看第三種情況"([)]",一開始左小括號"("進來,先壓入棧底,然後左中括號"["也進來,依然是把它留在棧中。

然後就來到右小括號")",這時我們一查找,發現當前棧頂元素是左中括號"[", 它和右小括號")"沒法進行匹配,這種情況就可以直接返回不合法這個結果了。

第四種情況"((([]))",前四個元素"(((["都是左括號,所以都要進行壓棧操作。

這時候到第五個元素右中括號"]"要進來,查看當前棧頂元素"[",正好相匹配,於是把左中括號"["從棧頂推出(pop),接下來同理,剩下的兩個右小括號也能和棧裏面的左小括號匹配,整個字符串走完一遍之後,我們發現最後的的棧不爲空,還剩一個未被匹配的左小括號"("在裏面。所以這種情況也是不合法的。

最後一種情況"]][["更簡單,一開始就是一個右括號,那我們直接返回不合法就可以了。

最後我們來算一下,用這種辦法進行判斷的話,它的時間複雜度和空間複雜度是多少呢?

首先,每一個元素進棧和出棧的操作是 O(1) 的,也就是一次性的操作,那麼每一個元素都會進這個棧一次,而且僅進入一次,所以就是 O(n) 的時間複雜度。

它的空間複雜度也是一樣的道理,最壞的情況下,所有元素都會壓在這個棧裏面,所以它在空間上也是 O(n) 的複雜度。

接下來我們來看代碼。

首先要說明的是,大部分人在寫這道題的代碼的時候,稍微不注意就可能寫得很冗餘、很複雜,這樣在面試中可能就會扣掉一些分數。

那我們來看一段比較優秀的代碼:

首先,第三行這裏用了一個比較高級的處理方式,就是用一個字典來把左右括號的對應關係存起來。同時,這裏還有一個比較有趣的技術處理,就是所有的右括號放在前面,也就是作爲字典的 key,接下來我們可以看到它這麼做的一個好處是什麼。

再看下面的代碼,根據我們之前講的思路,先對字符串 s 中的元素 c 進行遍歷,如果這個 c 不在我們定義的這個字典 paren_map 中,就說明它不是右括號,而是一個左括號,不管是左中括號好也好,還是左小括號也好,只要是左括號,按照之前所說的處理思路,我們就把這個 c 放在棧裏面去。

如果 c 在 paren_map 裏面的話,就說米 c 是右括號,那我我們首先要做的就是判斷當前的棧是否爲空,如果爲空,就返回 false,如果不爲空,我們就看這個右括號能否和當前棧頂元素相匹配,如果不匹配,也返回 false。

最後,如果匹配流程都走完一遍之後,最後我們還要判斷什麼?就是判斷最終這個棧是否不爲空,也就是這段代碼的最後一行。

如果最終的棧不爲空的話,說明是不合法法的,如果爲空的話,則是是合法的。

整體的代碼建議大家多看一下,最好能和自己寫的代碼做一下對比,看看自己的代碼還有哪些可以優化的地方。

我們再來看看這道題的另外一種寫法:

這種寫法我從兩個方面來進行分析。

第一,這段代碼第一眼看上去會覺得比較簡潔,看起來也比較漂亮。

我們再仔細看它的邏輯,會發現它的邏輯也很簡單。關鍵邏輯就在第 5 行:如果這個字符串裏面有相鄰的左右匹配的括號,就把它消除掉,一直重複這個過程,如果到最後這個字符串可以爲空,就說明它裏面的所有括號都匹配成功了,也就說明是合法的,否則就不合法了。

這樣寫的話,邏輯比較直接,代碼看起來也挺漂亮,但是它有一個問題,就是它的時間複雜度不好判斷,它的時間複雜度在平均的情況下會達到二分之 N 平方的時間複雜度,也就是說相比起一開始用的棧這種方法,它在時間複雜度方面要略差一點。

在這種情況下,即使你的代碼看起來是比較漂亮,但如果碰到有經驗的面試官,看到你的時間複雜度其實是更差的,那就會在面試中被減掉一定的分數。

至此,如果您仔細學習了這道題的兩種解法的話,相信已經收穫滿滿。如果有興趣,不妨繼續看下去,更精進一步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章