The two most important days in your life are the day you were born and the day you find out why. -Mark Twain

顯示具有 Coding面試 標籤的文章。 顯示所有文章
顯示具有 Coding面試 標籤的文章。 顯示所有文章

#73 Coding面試 LeetCode#14 Longest Common Prefix

原題目網址是 https://leetcode.com/problems/longest-common-prefix/description/

這一題在 LeetCode 的難易度分類屬於簡單級,題目的意思是你會得到一個 string array,然後你得從這個 string array 裡面找到最長從頭開始的共同子字串.在題目裡有給了簡單的範例,例如 Input: ["flower","flow","flight"] , 則答案就是 "fl".如果找不到合適的子字串,就直接回傳空字串.同時題目也告訴你,input 字串只有 a 到 z 之間的小寫字母,不會有其他的符號,這個條件大大降低了答案的難易度,因為可以少一些大小寫辦別或特殊字元或文化上的處理.

解法的邏輯也相對單純.找出最短長度的字串,以它為基礎,然後去比對每一字串從頭開始的字母,以此類推做到有一個字母是不符合條件或是最短長度已經到達.

參考的程式碼如下:



這解法的時間複雜度是 O(n2),空間複雜度是 O(n).也許有更快速的解法等待你的尋找.

Help it helps,
Share:

#70 Coding面試 - LeetCode #5 Longest Palindromic Substring

原文網址 : https://leetcode.com/problems/longest-palindromic-substring

這一題與之前曾提過的試驗字串是否為 Palindromic 的題目蠻雷同的.如果搭配 Palindromic 字串的檢驗法用在這題上,可能的解法如下:



從上面的程式碼你可以看到是將所有可能的子字串從大到小的長度逐一檢查是否為 Palindromic.因為題目是要找最長的 Palindromic,所以子字串長度才會從大到小.上述寫法的空間複雜度是 O(1),但時間複雜度卻是 O(n3) ,主要因為有三個迴圈與字串長度是有關係的.雖然上述的寫法可以在合格的時間內通過 LeetCode 的 test cases,但若你提了這樣的 solution,面試官很有可能會再問你在空間複雜度上還有進步的空間.

如果你從第一篇文章看到現在的話,你應該可以發現我在許多文章都提過不少次 "加速" 的方法.最基本的做法就是用空間換時間.以這題目而言,如果有機會將時間複雜度往下降的話,較簡單做法便是增加空間複雜度,也就是用空間換時間的做法.以這一題來說,要如何用空間換時間呢 ? 簡單的思考是從 "重複" 的動作開始想.在這題中,什麼是重複的呢 ? 大字串往小字串開始找子字串比對時,這便是一堆重覆的過程.當大的字串逐一比對若沒能成功時,便會將字串縮短一個字,然後再逐一比對.在這裡你就可以發現大字串的比對動作和短一個字的字串比對動作有許多是重複的比對.如果可以將這些比對動作的結果先跑起來並且儲存著,而之後大字串要進行比對時,就不用真的地比對,而是到儲存結果的地方將結果讀出來就知道比對是成功還是失敗.當大字串縮短變小字串時,比對結果也是從 "查表" 而得,而不需要真的地進行比對.因此,這個題目比較好的做法如下:



以上這做法的時間複雜度是 O(n2),成功地下降了,同時空間複複度也上升變成 O(n2).這種 "查表" 的方法就是演算法裡所謂的 dynamic programming,如果你沒念過演算法的話,基本上很難寫出 dynamic programming 的方法.這需要一點時間的學習和試驗.未來的文章寫到演算法時,再來解釋多一點.

Share:

#66 Coding面試 LeetCode #24 Swap Nodes in Pairs

原題網址 : https://leetcode.com/problems/swap-nodes-in-pairs

題目與一般的 List 題目類似,主要都是在考 next pointer 的位置安排.這一題比較特別的是兩兩成雙來交換位置,交換的邏輯並不會太難,用心推演一下就能推導出來,只有處理第一個 node 時需要做點額外處理即可.題目中有特別規定答案只能使用 constant space,也就是指你使用記憶體的量不能和輸入的 List 大小有關係.除此之外,題目也要求不能更改 node 裡的值來做交換位置的方法,所以你該做的就像一般 List 的考題來換 next pointer.參考的程式碼如下:



Share:

#62 Coding面試 LeetCode #236 Lowest Common Ancestor of a Binary Tree

原文題目在 https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree

題目是說給你三個 TreeNode, 第一個 TreeNode 是這個樹的 root, 第二個和第三個 TreeNode 是這顆樹裡面任意兩個 TreeNode. 所以,題目都這樣說了,你就不用擔心它會給你一個不在這顆樹裡面的 TreeNode.題目問你要在任意給你樹裡面兩個 TreeNode 後,你要找出這兩個 TreeNode 最低位置的父節點.所謂最低位置是指離 root 越遠越好.

看到這題目便想到跟 TreeNode 往上走到 root 的路徑有關,因為你要找的就是從任意兩 TreeNode 出發,會在那一個 node 第一次相遇.有一個很直覺的想法是在這兩個 node 一起往上走,但問題來了,你怎麼知道往上走要走到那去呢 ? 再者,任意兩 node 一起往上走不見得會在最小高度的 ancestor node 剛好碰在一起,因為任意兩 node 的高度不見得是一樣的.我當初想這一題時,便沒有想到要讓兩個 node 一起往上走,而是先讓其中一個 node 一直往上走到 root,把經過的 node 都記錄下來,完成之後便開始另一個 node 往上走,每往上走一個 node 時便比較第一個 node 是不是有走過同樣的 node,如果有的話,就代表找到了,如果沒有的話,就繼續往上走,一直到找到為止.

因此,我們需要把整顆樹的 "路徑" 和第一個參數 TreeNode 往上走的 "路徑" 記錄下來.在這裡,比較特別的是用 Dictionary (Hash table),而不是用 List.第二個參數的 node 只要在每一層往上走的過程中,檢查該位置的 node 是否在於第一個參數 TreeNode 的 "路徑" 就好了,其參考程式碼如下:



為何 Root 和第一個參數 TreeNode 的路徑是用 Dictionary (Hash table),而不是用 Stack/Queue 或 List 來記錄路徑.答案在以前的文章已經說明過了,這點就留給讀者想一想.

Share:

#60 Coding面試 LeetCode #199 Binary Tree Right Side View

原文網址在這裡

這題是一個標準的走訪樹節點的題目.這題多一個限制,就是只抓出每一層最右邊的節點.因此,最簡單的方法就是用 breadth first 的走法,把每一層逐漸地一層層往下走.在每一層走完要往下一層走之前,把該層最後一個走到的節點儲存到欲輸出的 List 上即可.這題參考的程式碼如下:



通常來說你不會遇到一模一樣的面試題目.我能想到有關這一題的變化就是要能得到左右兩邊看的結果,或者是主考官規定你要用 depth-first 的走法來得到答案.有興趣的話,不妨試著寫寫這兩種變化題目.

Share:

#56 Coding面試 LeetCode #230. Kth Smallest Element in a BST

原文的題目網址在這

有好長一段時間沒有到 LeetCode 網站上去看題目了,今天去的時候才發現 LeetCode 網站做了一些小改變並且增加了更多的題目.不僅演算法類的題目增加,也新增了其他種類.如 OO 設計,系統設計等.真的是稱的上軟體工程師面試題目的最佳網站了.我以前在 LeetCode 上寫了很多題目,這網站有一個優點就是它會把你之前寫過的程式碼保留下來,所以我還能看到我之前寫的答案.

這一題是考找出 BST 中第 K 個最小值.要解決這題,有兩個先決條件.第一,你得知道什麼是 BST (binary search tree),第二,你得知道第 K 個最小值是怎麼來的.基本上,如果遇到 TREE 這方面的題目都考 "特質".因此,得先把每個 TREE 的最重要特質記在心裡.BST 最重要的特質就是,在任意一個 tree node 上,其左邊 node 的值比自己小,而右邊 node 的值比自己大.比如下面這邊小樹

5
    / \
   3   7

接著,怎麼找第 K 個最小值 ? 凡是遇到 tree 的題目要你比大小或是找第幾個這類跟順序有關的,一定要想到 tree traversal.Tree traversal 有三種,in-order, pre-order, 以及 post-order.其中 in-order 的走法用在 BST 上時,把樹走完後,拜訪的順序剛好就會把值從小排到大.以上圖而例就是 3->5->7,因此想找第 K 個最小值剛好就等於 in-order 走法拜訪的第 K 個 tree node.

如果以上都沒問題的話,接著就直接寫程式了.Tree 的拜訪我一向習慣用的是 iterative 的方法來寫.所以寫出來的程式如下:



如果你在面試時遇到這類的問題,我都會建議用 iterative 的寫法.這能避免使用 recursive,避免的原因就是在於當 tree 很大很大時, recursive 會造成 call stack overflow,這是做產品的公司所不能接收的大禁.

這是我以前的答案,使用 Stack 來記錄拜訪 BST 的走過痕跡.當我再重新 submit 到 LeetCode judge system 後,它告訴我上述的程式花了 189ms 跑完,在 C# 的答案裡面打敗了 41% 的答案.這表示有 59% 的答案效能跑的比這程式還快.看來這程式用了 Stack 果然還是有影響.為了追求更好的效能,把程式改成如下:



再重新讓 LeetCode judge system 測驗後,這次它回傳此程式的效能打敗了 68% 的答案.顯然 32% 的答案有更快的效能,的確很利害.不過 LeetCode 這一題的 test case 中並沒有很大很大的樹,所以用 recursive 的寫法並沒有造成 stack overflow 的現象.
Share:

#53 Coding 面試- LeetCode #143 Reorder List

題目的網址 https://leetcode.com/problems/reorder-list/

這題跟一般的 List 考題一樣,都是把 List element 做順序上的改變.這題的順序改變也比較特別,因為是一前一後一前一後的順序.

一般來說,List 考這種順序排列的考題時,通常都不希望你用到額外的記憶體空間,也就是說所有的動作都要在原來的 List 空間上完成.記得當初在寫這題時,花了不少時間在想要怎麼排出新順序,後來才想到把 List 對折,這樣就變成第一個對到最後一個,第二個對到最後第二個,以此類推,中間的過程也要將成雙成對的配對中把前一對的尾色連到後一對的頭,這樣就完成了.考這種題目真的蠻討論的,因為要很快想到折一半再成雙成對的搭配起來還真不容易.然而,List 的題目就是這樣,只要想法正確了,寫程式就不是問題了.



以上的解決,Space complexity 是 O(1) ,而 Time complexity 是 O(n)


Share:

#51 Coding 面試 - LeetCode #9 Palindrome Number

原文網址 https://leetcode.com/problems/palindrome-number/

這一題跟之前講的有一題 palindrome string 有點異巧同工之妙,但不同的是這題指的是數字,而不是字串.如果你把數字當成字母來看待的話,則也能用之前相同的解法.這在演算法來說算是一種 reduction 的技巧.然後,數字和字串的特性畢竟不同,所以若把數字轉成字串來處理是可以的,但就浪費了一些數字的特質.

其實要把數字反轉,最簡單的方法就是用十來取餘數,也就是說 2 取十的餘數是 2 , 12 取十的餘數是 2 , 102 數十的餘數是 2.因此,對十取餘數之後,我們就能得到個位數的值,然後再對商再取一次餘數,用這個方法一直做到商變成 0,這樣子就可以把原來的數字一個一個取了出來.

剩下的就是將取出來的數字排好,第一個取出來的放最前面,最後取出來的放最後面,這裡不需要 "儲存" 的觀念來做,其實只要在每取出一次餘數時,把之前取出來的數字乘十就行了,這樣就等於先取出來的會被 "進位",然後再加上最新取出來的餘數.

最後,還要記得處理正負號.這只需要在一開始的時候判斷是正號或負號,若是負號的話,就一律不符合答案,若是正號的話,就檢查反轉前和反轉後的數字是不是一樣的,這樣就完成了.

參考的程式碼如下:



Share:

#50 Coding 面試 - LeetCode #349 Intersection of Two Arrays

原文網址 https://leetcode.com/problems/intersection-of-two-arrays

這一題在原文網址上列為簡易型的題目,看了題目之後,只要你熟悉一些基本的資料結構,應該可以很快想出不錯的解法.題目輸入是兩個 integer array,然後輸出一個 array,這個 array 包含的元素要出現在兩個輸入的 array 裡.

若用最直覺的想法,你可以做一個 for loop 在第一個輸入 array 上,把每一個元素和第二個輸入 array 的元素相比較.以這樣的做法來看,時間複雜度將會是 O(n2).以這個題目來說,這樣的時間複雜度並不理想.所以得再想想一個比較好的處理方式.

我在寫這題時一開始就想到利用 hash table 來運作,因為 hash table 有極佳的 O(1) 的動作速度.所以一開始把第一個輸入 array 所有的元素透過一個 for loop 加入到 hash table.然後再將第二個輸入 array 利用另一個 for loop 來查看是否有同樣的數字已經存在於 hash table 之中.如果有的話,它就是我們要尋找的對象,但把它加入到輸出的結果裡,這裡是用一個 List 來代表.最後再將整個 List 轉換成 array 輸出,這樣的轉換只需要 O(n). 因此,整個時間複雜度將是 O(n+n+n).有二個 for loop 和最後的轉換,所以一共是三個 n.程式碼如下:



最後我還發現了 LeetCode 提供了與其他同樣語言解法的比較.上述的解法打敗使用同樣語言 75% 的解法.我想這是以他們電腦執行速度來計算的.由此可見,上述的程式碼在最佳化上還有很大的空間可以改進.

Share:

#46 Coding 面試 - LeetCode #62 Unique Paths

原文網址 https://leetcode.com/problems/unique-paths/

這一題主要是考 dynamic programming 的思考,但老實說,這題過於直覺,所以寫完答案時還沒馬上觀察到這是 dynamic programming 的解法


參考的解法如下:



從 First row 上的可行走法都是1, 因為只有可能從左邊走過來的方式.從 second row 開始,就有可能是從上面或從左邊來的走法,因此才是相加的,也就是上述解法中 r[i,j] = r[i-1,j] + r[i,j-1];

我相信如果你的主考官想要考你 dynamic programming 的話,應該不會用這一題來考你.若是要考簡單的,應該會是用 Fibonacci number 來考你,因為最直覺的 Fibonacci number 的寫法並不是用 dynamic programming 的寫法,而是用 recursive 的寫法.以時間複雜度來看,dynamic programming 的寫法是比較快的,而以空間複雜度來看,recursive 寫法是比較優的.

Share:

#44 Coding 面試 - 列出公司組織人數

這一題是我以前面試時遇到的題目,不知道當初面試的老闆是在那找來的題目,總之我沒有看過類似題.先來說明題目,

你要寫一個程式,程式的 input 是一個字串,這字串包含了公司裡員工的名字,同時字串裡也包含了老闆與直屬員工的關係.以下是一個例子

A-B,C-B,B-D   這字串代表 A 的老闆是 B,C的老闆是 B, B 的老闆是 D,以此類推,中間會用逗號隔開每一組資料,然後一橫代表老闆和員工,左邊是員工,右邊是老闆.所以,這一個字串其實上就代表了整個公司的組織圖.你可以用手動的方式把每個資訊解析一下就可以把這組織圖的 tree 給畫了出來.

題目要求你要列出某一層次的老闆們下面一共有多少員工 ? 以上述的例子來說,D 是在 level 1, B 是在 level 2, A 和 C 是在 level 3,如果要求你列出 level 2 的老闆時,你的程式就要顯示 B 有兩個員工.

看到這裡,不知道你心裡有沒有想法要怎麼解決這一題.當初這個面試更特別的是,這位面試的老闆帶他自己的筆電過來,Visual Studio 已經開好了,題目的 function 和輸入字串也都打好了,直接要我寫 function 裡面需要的 code.就是上機考.這是我第一次面試遇到上機考,以前遇過的面試都是寫白板而己.寫白板的好處就是有一點小錯誤的話也沒關係,因為面試者主要是看你寫的邏輯對不對,所以不會在乎你是不是打錯 method name 或少了結尾分號之類的.但上機考就完全不一樣了,你一定要讓 compiler 很開心,不然既便有一個再小的錯誤,compile 會失敗的.

基本上,只有 30 分鐘可以這一題,我一開始想了一下,難道真的要建 tree 嗎 ?  寫了建立 tree 的功能之後,還要加上 tree traversal 的功能,這樣才能在 tree 上遊走找人數.當時我還很認真地思考要如何做 tree 和相關功能.後來想想不太對,因為這樣太麻煩了,我只剩 25 分鐘而己.後來就想了一招,雖然不是很好,但是用來解這個題目一定夠用.那就是改用 recursive 的觀念來做.做法如下

1. Parse 輸入字串來建立一個 hash table,key 是員工名,value 是老闆名
2. 用 recursive 的方式在這個 hash table 上找出某一個 level 的員工名稱
3. 根據步驟 2 的結果,把 hash table 上所有員工也是用 recursive 的方式來找看誰的上面老闆是在步驟 2 的結果內,同時把該老闆的下屬員工數做人數統計

因為老闆給的公司組織是真實的資料,所以我知道當我用 recursive 的方式來運作時並不會發生 call stack overflow 的現象.這個觀念在前面的文章中曾提過.

結果,最後我用了 40 分鐘才把整個程式做完,然後直接跑程式給面試老闆看,接下來就是討論我如何做,有沒有什麼改進之處等等.

很抱歉,當時的程式碼就在那面試老闆的電腦裡,沒機會貼在這裡供大家參考.但還是把我當時的做法寫出來供大家參考.

如果你看到在那邊有相似的題目,再麻煩你分享網址.^_^

Share:

#40 Coding 面試 - LeetCode #150 Evaluate Reverse Polish Notation

原文題目網址 : https://leetcode.com/problems/evaluate-reverse-polish-notation/

這題蠻有趣的.剛看到這題時,老實說並不知道什麼是 Reverse Polish Notation.如果你在被考到這題時也不知道的話,其實也沒關係,因為你可以直接問主考官,主考官一定會告訴你什麼是 Reverse Polish Notation.因為你要懂了這是什麼表示法,你才能做這個題目.

簡單的說,一般的數學,我們習慣寫成像我們從小到大看到的式子,例如 3+4.而 Reverse Polish Notation 就會寫成 34+ ,很簡單吧! 所以你看到題目上給你的例子, 2,1,+,3,* 就是 (2+1) * 3 ,因此答案就是 9.所以,這一題就是要考你寫一個 function,輸入是 Reverse Polish Notation,而輸出是數字答案.因此,我們假設輸入是一個 string array,而輸出是 int.

不知道你看到這種題目時,你會不會馬上聯想到 stack 或是 queue 之類的資料結構,因為可以依序地把數字儲存下來,又可以正向或反向地把數字取出來.如果你能想到這個,解題的方向也算是對的了.

除此之外,一開始還要為輸入字串做檢查,檢查是不是合格可運算的.例如,如果輸入字串都是運算符號而沒有數字,那這是不能算的,需要回傳錯誤.或者是輸入字串只有數字而沒有運算符號,這也是不能計算的.另外,輸入字串的第一個和第二個元素一定不會是運算符號,一定會是數字.所以這幾個條件就當做是程式裡的 throw exception 的條件.

接著,只需要一個 for loop ,把符合所有條件的數字用 stack 記錄下來,然後遇到運算符號時,把從 stack 把之前的數字拿出來運算,算完後再寫回去 stack.所以,用這樣的邏輯就可以把這題的答案寫出來了.

程式碼參考如下:


Share:

#38 Coding 面試 - LeetCode #2 Add Two Numbers

原文題目 https://leetcode.com/problems/add-two-numbers/

這題是個基本的 Linked List 考題,如果題到這類型的 List 題目,若題目沒特別說的話,就一律想成是 Single Linked List 了.其實,我個人不是很喜歡 List 考題,原因是大部份的問題都蠻無聊的,可能就在 List 上把元素搬來搬去,而且不一定會有一個很好的邏輯來搬這些元素,所以就會造成一個 function 寫的長長的.像這一題就是這樣.

題目給了兩個 input list,要把每個元素加在一起後,然後輸出一個最後的 list.其實也蠻簡單的,你只要在這兩個 input list 上一個一個元素去拜訪,然後把加起來的答案寫到新的位置上,同時再注意進位的事情.並且你也要考慮到兩個 input list 的長度可能是一樣長,也可能是不一樣長,甚至可能是空的.如果你按照這個想法寫,答案就可以寫的出來.

但是,就這麼簡單嗎 ? 基本上是的,但要注意一件事,上面的想法是會製造出另一個新的 list,算到最後把這個 list return 出去.這樣做的話就等於空間複雜度是 O(n+m) 了,假設 n,m 分別是兩個 input list 的長度.

所以,比較好的做法是讓空間複雜度變成 O(1),也就是說每當計算一次元素的加法時,就把答案寫到某一個 input list 上,最後 return 這一個 input list 就行了,所以就不需要宣告出其他不是 O(1) 的空間.

參考的程式碼如下:



沒錯吧,又臭又長! 如果你能想到更好的方法來簡化以上的程式碼,再麻煩你教教我! ^_^

Share:

#35 Coding面試 - LeetCode #94 Binary Tree Inorder Traversal

這題目出現在這 https://leetcode.com/problems/binary-tree-inorder-traversal/

如果要考 Tree 相關的題目,這一題算是相當經典的考試題目了.我想經典的原因就是 Tree inorder traversal 是資料結構課本裡面一定會教到的內容.大部份的課本裡面都會提供 recursive 的方式來做 inorder traversal,其程式如下

        public List<int> InorderTraversal(TreeNode root)
        {
            List<int> result = new List<int> ();
            if (root == null) return result;
            if (root.left != null)
                result.AddRange(InorderTraversal(root.left));
            result.Add(root.val);
            if (root.right != null)
                result.AddRange(InorderTraversal(root.right));
            return result;
        }

但面試官既然要考你這一題的話,絕對不可能只問你 recursive 如何寫,也一定還會問你怎麼用 iterative 的方式來寫.
如果你完全沒做過相關的練習,一時之間還真的很難想的出來該怎麼把 recursive 改成 iterative. 在這可以分享一件小事,因為寫 recursive 有階層的關係,所以程式的 call stack 就一層一層往上加,上一層結束回到下一層時也自然知道該從什麼地方繼續執行.但是若改用 iterative 的話,就沒有這種記住上次執行到那的好處了.對於這種需要記住位置的情況,就可以直接想想 Stack,因為 Stack 能幫助我們記住走過的痕跡.

所以,改成 iterative 的程式碼如下

        public List<int> InorderTraversal(TreeNode root)
        {
            List<int> result = new List<int> ();
            if (root == null) return result;
            Stack<TreeNode> stack = new Stack<TreeNode>();
            TreeNode node = root;
            while (stack.Count != 0 || node != null)
            {
                if (node != null)
                {
                    stack.Push(node);     // <-- 幫我們記住位置
                    node = node.left;
                }
                else
                {
                    TreeNode temp = stack.Pop();    // <-- 把上次記住的位置拿出來
                    result.Add(temp.val);
                    node = temp.right;
                }                              
            }
            return result;
        }

沒感覺嗎 ? 多寫幾次就會有了.

Share:

#30 Coding面試 - LeetCode #125 Valid Palindrome

題目的網址: https://leetcode.com/problems/valid-palindrome/

這一種題目算是蠻基本的,以前在台灣找工作時曾遇過有個公司的考卷出這一題,而在美國找工作時,目前還沒遇過有人考這一題或是相似的題目.

基本上,這題就是要寫一個 function 來驗證輸入的字串參數是不是 palindrome.所謂的 palindrome 就是字串中第一個位置和最後一個位置的值是一樣,第二個位置和倒數第二個位置的值是一樣,以此類推.所以一個很簡單的想法就是只要一個 for loop 就可以做完這樣的工作,而且不需要線性成長的空間,因此時間上是 O(n),空間上是O(1),這是對答案的要求了.

但 LeetCode 出的這一題有一點點小小的變化,因為輸入字串中可能會有其他的符號,而這些符號是不列入規則的,所以題目上寫 "A man, a plan, a canal: Panama" 是一個合格的 palindrome,因為只要不是符號類的字元都一律跳過.因為有這樣的小變化,我們除了用一個變數來記錄前面開始的比較位置,也還多用了一個變數來記錄從後面過來的比較位置.由於這是固定的兩個變數,數量不會隨著輸入參數而改變,所以在記憶體空間使用上是固定的.

另外,我們透過一個基本的 function (IsLetterOrDigit) 來幫助我們辦別輸入字元是字母還是數字,我們假設 IsLetterOrDigit 的時間複雜度是 O(1).實際上 O(1) 是可以做的到.最後,整個程式如下:



這個程式碼的時間複雜度是 O(n) , 而空間複雜度是 O(1).
這是一個很基本的考題,如果你的面試遇到這種題目,那表示面試官根本不想為難你.




Share:

#27 Coding 面試 Leetcode #98 Validate Binary Search Tree

我記得這一題我被問過兩次,是兩個不同的團隊,而且都是跟資料庫有關的工作.所以,若你也要尋找資料庫相關的開發工作,看來 Tree 的題目是很難避免的.

這題的題目很單純,就是要寫一個 function 用來驗證題目的 Tree 是不是 Binary Search Tree (BST).
根據 BST 的定義,左邊的節點值必需小於父節點值,而右邊的節點值必需大於父節點值.
但這題有一點要小心的是,別只是檢查父節點值而己,應該要檢查所有上層的父節點值都要小於或是大於.

如果題目只給你 tree root,那麼你可以寫出像下面的 function 來檢查這個 tree 是不是 BST.

        public bool IsValidBST(TreeNode root)
        {
            return BST(root, null, null);
        }

        bool BST(TreeNode node, int? min, int? max)
        {
            if (node == null) return true;
            if (min == null && max == null)
            {
                    return BST(node.left, min, node.val ) && BST(node.right, node.val , max);
            }
            else if (min != null && max == null)
            {
                if (node.val > min )
                    return BST(node.left, min, node.val) && BST(node.right, node.val, max);
            }
            else if (min == null && max !=null)
            {
                if ( node.val < max)
                    return BST(node.left, min, node.val) && BST(node.right, node.val, max);
            }
            else
            {
                if (node.val > min && node.val < max)
                    return BST(node.left, min, node.val) && BST(node.right, node.val, max);
            }
            return false;
        }

若你仔細看的話,我是把每個分支出去時可能的節點值範圍也傳遞過去.這樣的寫法也許不是最好,但就如之前文章所說,要在五分鐘內想好然後在半小時內寫完再測試,所以就勉強用了.

這時候如果遇到要求更高的面試人員,他可能會說 recursive 的方法不適合用在 tree,所以要改成 iterative 的方式來寫.
其實要改成 iterative 的方式,說難不難,說簡單也不簡單,因為要在那短時間想出來也是件不容易的事.比較幸運的是之前我有練習過 BST 用 In-order traversal 的走法來驗證 BST,所以這題也就順利寫完了,以下就是改成 iterative 的方式,用 In-order 走訪 BST 之後,得到的答案一定是從小排到大的數值,所以只要多一個 for loop 來檢查數值是不是從小排到大即可.

  public bool IsValidBST(TreeNode root)
        {
            if (root == null) return true;
            List<int> re = InOrder(root);
             if (re.Count == 1) return true;
            for (int i = 0; i < re.Count - 1; i++)
            {
                if (re[i] >= re[i + 1])
                    return false;
            }
            return true;
        }

        List<int> InOrder(TreeNode node)
        {
            List<int> result = new List<int>();
            Stack<TreeNode> stack = new Stack<TreeNode>();
            while (stack.Count != 0 || node != null)
            {
                if (node != null)
                {
                    stack.Push(node);
                    node = node.left;
                }
                else
                {
                    TreeNode t = stack.Pop();
                    result.Add(t.val);
                    node = t.right;
                }
            }
            return result;
        }

雖然在初步面談中都把上述的題目做完了,但這也不代表就有機會可以繼續做後續的面試.所以,很可惜沒能做成資料庫開發的工作,找工作真的一切都是個緣份.





Share:

#23 Coding 面試 Leetcode #73 Set Matrix Zeroes

我打算把以前遇到的一些在面試時遇到的一些特別經驗或感想記錄在這電腦科學筆記裡,一方面,在面試時會遇到的問題大部份都是基礎的電腦科學題目,二方面也把這些過程記錄下來,當做是一種紀念吧!

在美國的軟體行業裡,要做 coding 面試算是很正常的事情,而這些 coding 面試大部份都是資料結構和演算法的內容,所以都是大學的必修課.但其實有許多題目還真的不是光念過課本就能想的到,那些題目還真的需要多練習.在市面上有許多網站會列出一些常見的面試考題,而我之前最常去的網站就是 Leetcode. 這網站不僅記載了許多考題,而且還有 online judge 可以直接測驗你的程式碼是否正確.

今天來聊聊這一題,在 Leetcode 網站上的第 73 題.我被問過一模一樣的題目,面試者連改都沒有改,而這一題也讓我體會到另一種空間複雜度的境界.

第 73 題的題目是指,給你一個 m x n 的矩陣,如果在第 (i,j) 的位置上出現 0 時,就要把 row i 和 column j 的元素全部變成 0.剛看到這個題目,覺得不會很難.最直覺的方法是你可以宣告兩個 Array 或 List 來記錄那些 row 和 column 裡面有 0 ,最後再依據 Array 或 List 的內容把相關 row 和 column 的內容變成 0.如果你是這樣想的,其實這方法也沒什麼不好,只是要多浪費一點空間,因為另外宣告了額外的 Array/List 來記錄那些 row, column 有 0 的元素存在,而且 Array/List 的大小會根據矩陣的大小而改變,因此在空間複雜度上就不是 constant 了,而是 O(m+n).如果這時候面試官沒繼續要求的話,那基本上就過關了.但生活中有時很難會如此順利,面試官很可能會要求你在空間複雜度上做到 constant space.也就是說你可以用其他的空間,但是這些額外的空間不能隨著矩陣大小變化而改變.

既然是這樣規定的話,這題目就真的變得有相當的難度了.我自己在做這題目時也無法在五分鐘之內想到好的解答.為什麼是五分鐘呢 ? 因為一個面試通常是一小時,其中做 coding 考試的時候大約有三十分鐘,所以在五分鐘之內沒有想出正確答案的話,那就很難可以在剩下的時間把程度寫完在白板或電腦上.
後來,我參考了網路上其他人的解法,我找到一個蠻好的解法.基本上,這個解法要宣告兩個基本的變數,這是  constant space,而它把每個 row, column 有 0 的資訊直接記錄在輸入矩陣之中,因此,根本就不需要用到非 constant space 的空間了.



如果你用心把這個解法看完的話,你一定也會覺得這方法實在太妙了,而且保證你對空間複雜度的應用會有更高一層的體會.原來把 input parameter 的空間拿來做為暫存空間,也是一種省空間的好方法.

Share: