[設計題] 17道經典設計題串講

17道經典設計題串講

最近正在面試uber,uber以頻繁考察系統設計而著名,系統設計是一個沒有標準答案的open-end問題,因此關鍵在於對於特定問題的設計選擇,俗稱trade-off,這個思惟平時無處鍛鍊,故開啓此文,記錄學習和理解的過程。javascript

本文所有題目和分析來自《Elements Of Programming Interviews》的 Design Problems 章節,我只是在他的基礎上wrap了本身的理解,儘可能通俗地表達。html

設計題的三個要點:
1.組件分解
2.並行計算
3.利用緩存java

1. Spell Checker

場景:

給一個word,假設這是一個拼錯的英文單詞,咱們應返回一個list of word,list裏的words都是word可能的正確拼寫。web

分析:

有兩個概念要clarify:
1.對於「拼錯"這個概念,什麼叫"拼錯",什麼叫"拼對",都是相對的,咱們能夠假設一本詞典,這裏面全部的單詞都是拼對的,其餘的都是拼錯的。還有一個延伸就是,若是這個詞」拼對「了,程序還check不check呢?
2.給一個word,咱們先生成一堆和他很像的詞,這些詞可能拼對也可能拼錯,咱們把拼對的返回,問題在於,這裏的」像「怎麼界定?這裏咱們假設 - 「像」,表示全部和word的edit distance(Levenshtein distance) exactly爲2的單詞們。面試

數據結構:

把詞典預處理成一個map,key是全部的字典單詞,value無所謂。咱們只想快速的知道這個word是否是詞典(拼對)的,對這個單詞的其餘性質並不關心。算法

核心算法:

給一個word,生成全部與這個word的edit distance(只substitute,不add也不remove)爲2的全部string來,挨個去查hashmap。查出來的這些按照某種rank algorithm排序選出最高的10條。
至於這個排序算法,請看本題的後續部分。編程

複雜度:

對於一個request_spell_check(String word), 假設字母有m(英語的話就是26)個,單詞長n(通常來說1~15)
時間O(m^2 * n^2),由於設定edit distance爲2,改裝一個word的可能性有n(n-1)(m-1)^2/2種,假設改寫一種是一條java語句(一次循環),那麼改寫一個interesting(長度爲11)大約須要循環次數爲:34375,假設1s內一臺電腦能夠運行10^8條java語句,那麼每一個request的耗時大約是0.3ms,1ms這個級別,是能夠接受的。
空間O(1),經過上面的查找,查找的結果可能會很是大,咱們只要返回一個ranked list of suggestions就行了,好比rank最高的十條。數組

後續:

關於排序算法,輸入是一個list of word,這些word都在詞典裏,是拼對的詞,如今要從裏面找出10條最多是答案的詞,明顯是一個沒有標準答案的問題,因此要根據狀況分析trade-off:緩存

  1. Typing errors model,咱們的排序就要根據鍵盤的layout來判斷了;數據結構

  2. Phonetic modeling,情景:我想打一個詞,只會讀,不會寫,好比kat打出了cat。解決方法是,把輸入的單詞按phonemes劃分,每段按發音進行替換。

  3. History of refinements,蒐集用戶打錯的數據 - 用戶常常會打一個詞,而後刪了,打了另外一個詞,而後request請求,這兩個詞第一個詞就是潛在的錯誤拼寫,第二個是正確拼寫。

  4. Stemming,經過把["computers", "computer", "compute", "computing"]提取爲"compute",當咱們拿到一個word時,對他stemming,而後把stemming結果的列表返回。

2. Stemming Problem

場景:

Stemming是搜索引擎normalize環節的一個技術,中文叫作詞幹提取,顧名思義,即把["computers", "computer", "compute", "computing"]提取爲"compute"。Stemming既用在query string上,也用在document的存儲中。前者能夠延伸語義,好比查compute的時候極可能對computer感興趣或者你想查的根本就是computer。後者能夠大大下降搜索引擎索引的存儲大小,也能夠提升搜索效率。

分析:

Stemming明顯是一個沒有標準答案的問題,基本上是在trade-off,根據情景解決問題。並且這個題目太大,這裏只是簡述。
本質上Stemming就是根據某種規則(rule)重寫(rewrite)。

  • 重寫,基本都是focus在後綴(suffix)上,改後綴(wolves -> wolf),刪後綴(cars -> car)。

  • 規則,這些規則乾的事兒就是,先找到哪兒到哪兒是後綴(應該有個後綴表什麼的),而後把這個後綴transform一下(應該也有個轉換表什麼的)。

一個有效的實現方法是根據這些規則創建一個Finite State Machine - 有限狀態機。

後續:

越sophisticated的system會有更多的exceptions,即考慮更多地特殊狀況,好比Chinese -> China這種。
Porter stemmer,做者Martin Porter,目前是公認最好的英語stemming algorithm。
其餘的方法有:stochastic methods, n-gram based approaches.

3. Plagiarism Detector

場景:

在一堆文章裏找類似的文章對們。
這裏文章咱們看作String,兩篇文章若是有長度爲k的公共substring,就說他們是類似的。

分析:

對某個String(文章),假設這個String長度爲len,那麼對這個String計算len-k+1個hashcode。
舉例好比:"abcdefg"這篇文章,取k=3,咱們對"abc","bcd","cde","def","efg"這5個substring分別作5次hash,存到一個大大的HashTable裏,key爲hash的值,value爲文章id以及offset。
這樣當遇到collision(這裏不是指發生了多對一映射,假設hashfunction很好能夠一對一映射)的時候,咱們就知道那兩篇文章是類似的。
這裏就須要一個能夠incrementally更新的hash算法,這種hash方法叫rolling hash,可參見Rabin-Karp算法,Leetcode這道題 - 187. Repeated DNA Sequences

這裏說一下爲何hash算法取模的size都是質數:

好的HASH函數須要把原始數據均勻地分佈到HASH數組裏

原始數據不大會是真正的隨機的,可能有某些規律,

好比大部分是偶數,這時候若是HASH數組容量是偶數,容易使原始數據HASH後不會均勻分佈。 好比 2 4 6 8 10 12這6個數,若是對
6 取餘 獲得 2 4 0 2 4 0 只會獲得3種HASH值,衝突會不少 若是對 7 取餘 獲得 2 4 6 1 3 5
獲得6種HASH值,沒有衝突

一樣地,若是數據都是3的倍數,而HASH數組容量是3的倍數,HASH後也容易有衝突 用一個質數則會減小衝突的機率

這個HashTable的size要很是大,這樣不一樣substring映射到同一個hashcode的概率會小。
若是想要省空間,且對結果的精確度要求不高,能夠不存全部的substring的hashcode,而只是存特定的hashcode,好比最後5個bit都是0的hashcode,這樣咱們只存全部substring的1/(2^5)便可。
上面的方法存在不少false positive,好比假設這裏文章指的是html頁面,全部的文章幾乎在開頭都會調用一樣的javascript段,咱們的方法就會認爲這些html都是類似的。這裏咱們就須要在判斷前,對這些文章進行預處理,把headers去掉。
上述的方法在每篇文章(String)都很長,且這些文章分佈在不少servers的時候,尤爲好用。mapreduce就很適合幹這個事。

4. Pair Users By Attributes

場景:

在social network裏,每一個用戶(user)有一些屬性(attributes),如今咱們要把屬性相同的用戶配對。

分析:

用戶怎麼表示:用戶id - 32位的int
屬性怎麼表示:strings
怎麼叫配對:每次讀入一個user(有他的id和屬性們),從以前未成功配對的集合裏找有相同屬性們的用戶。

數據結構:

map。

算法:

第一個層面:brute force。
假設連續讀入n個用戶,對於每一個讀入的用戶,與unpaired set裏每一個用戶進行attributes的比對,時間複雜度爲O(N^2).
第二個層面:hashtable,key存attributes組合,value存用戶。
問題是怎麼把一堆attributes的組合變成一個key,即這個hashfunction怎麼搞?
若是屬性不多,只有幾個,好比只有"藍色","綠色","紅色","吉他","鋼琴","提親","爵士鼓",那徹底能夠用一個bit array一波帶走,而後把這個bit array隨便hash一下。
時間複雜度O(nm),n爲用戶數,m是全部distinct attribute數,對於上面的例子就是7。時間消耗就是把每一個用戶的attributes轉化成bit array而後把bit array哈希的時間。
第三個層面:若是attribute有不少,有1億個,且大部分的用戶只有不多的幾條attributes,這樣上面的方法就很差用了,對於每個用戶都要看這一億個attribute裏有沒有,是浪費時間的。
在這麼一個sparse subsets裏,更好的方法是,直接把這些attribute們concatenate成一個string,固然concatenation以前先排個序,而後對這個string使用hash function。
時間複雜度是O(NM),n是用戶數,m是平均每一個用戶的attributes concatenation以後的長度。

5. Detect Video Copyright Infringement

場景:

故事是這樣的:有一天華誼發現youku.com上有人上傳了」讓子彈飛高清藍光「整部電影,這侵犯了華誼的版權,假設你是華誼的工程師,如何給定華誼的視頻庫,判斷youku.com哪些視頻是侵權的。

分析:

和第3題要乾的事兒同樣,可是把document換成了video。同理也能夠換成audio,這裏討論video。
判斷對象是video的話,就不能像document那麼幹了(rolling hash),緣由在於即便內容相同(好比都是「讓子彈飛」),但不一樣的視頻能夠壓縮成不一樣的格式(flv,mp4,avi),有不一樣的分辨率(1366x768, 1024×768),不一樣的壓縮程度。

算法:

把全部視頻都轉換成相同的格式(format),分辨率(resolution)和壓縮度(level of compression)。注意,這麼作了以後,相同內容的視頻仍然不是相同的文件。可是,通過了這樣的normalization以後,咱們就能夠開始對每一個視頻生成signature了。

  • 最簡單的signature生成方法:對每一幀(frame)用一個bit根據一個亮度(brightness)閾值賦0或1,這個閾值是average的brightness。

  • 更好的signature生成方法:對每幀用三個bit表示R,G,B,每一個bit的取值根據R,G,B的intensity閾值賦0或1。

  • 再精確一點的signature生成方法:把每一幀分紅幾個region,每一個region求出一個R,G,B的3個bit,再把這些region contatenate起來。

後續:

以上算法是algorithmic的,還有其餘的手法:
讓用戶來標明是否侵權(有報酬的);和之前確認了的侵權視頻進行比對,看是否徹底同樣;看視頻header的meta-information,等等。

6. Design TeX

場景:

TeX是一個排版系統,寫論文,出版社全用它。好處是不依賴平臺,輸出是設備無關的。像JVM同樣。媽媽不再用擔憂Windows下編輯的書稿在Mac裏凌亂了。
一旦TeX處理了你的文件,你所獲得的 DVI 文件就能夠被送到任何輸出設備如打印機,屏幕等而且總會獲得相同的結果,而這與這些輸出設備的限制沒有任何關係。這說明 DVI 文件中全部的元素,從頁面設置到文本中字符的位置都被固定,不能更改。TeX是一個很是多才多藝的程序。它不但能夠編輯論文,書籍,幻燈片,學術雜誌,我的簡歷,還能夠 編輯曲譜,化學分子圖,電路圖,國際象棋,中國象棋,甚至圍棋棋譜,……事實上只有少許文檔不適合用TeX編輯。
問,怎麼設計TeX?

分析:

兩方面:

  1. building blocks,即基本元素,包括字體(fonts)和符號(symbols)怎麼存,怎麼表示?

    • 每一個基本元素(symbol和font)最簡單的表示方法是用2D array,俗稱bit-mapped representation.這樣作的問題是,對於resolution要求比較高的設備,這樣的空間消耗極大,且不說對一個font來講有不一樣大小,斜體,粗體之分,這都要另存一份。

    • 更好的方法是,咱們能夠用數學方程來定義symbols。這樣咱們反而能夠更精細地編程生成symbol和font,甚至能夠控制字體長寬比(aspect ratio),筆畫粗細(stroke width),字體的傾斜度(font slant),描邊粒度(serif size)。

  2. hierarchical layout,佈局,即怎麼把基本元素漂亮的擺放在一塊兒

    • 咱們能夠把元素(component)抽象成一個矩形的box,全部的都是矩形box,矩形box組裝起來成爲新的矩形Box.
      舉例好比:每一個symbol是個box(好比@,3,A,d,-,#),line(好比:---)和paragraph都是由box組成的,section title, tables, table entries 以及 images也都是由矩形Box組成的。至於把這些box漂亮地組裝起來就用到不少的算法。

後續:

其餘的問題還有:enabling cross-referencing, automatically creating indices, supporting colors, and outputting PDF.

7. Design a Search Engine

場景:

給100萬個documents,每一個平均10KB。輸入set of words,返回set of documents,其中每一個document都包含這個set of words。假設全部documents能夠存放在一臺電腦的RAM中。

分析:

很少說,inverted indices走起。
對於每一個索引(inverted index),至關於map的一個entry,key是word,value是sorted list of ['document_ID',offset],這裏list是先按document_ID,後按offset排序的。
在搜索的時候,好比搜索"beatles nirvana eminem",會分別對三個words找他們的sorted list(爲了方便,下面只顯示document_ID):
找到了三個詞對應的索引條目:
beatles: [1]->[3]->[120]->[789]->[1022]->[7881]->[9912]
nirvana: [1]->[2]->[3]->[456]
grammy : [2]->[3]->[120]
答案就是這三個list的交集,即[3]這個document,求交集的時間複雜度和三個list的長度總和成比例(proportional to the aggregate len of the sequences)。
固然咱們能夠作一些優化:

  1. Compression:既省空間又提升cache命中率。由於list都是排序了的,能夠用增量編碼法(delta compression),每一個documentId能夠省幾個bit。
    增量編碼又叫差分編碼,簡單地說:不存「2, 4, 6, 9, 7」,而是存「2, 2, 2, 3, -2」。單獨使用用處不大,可是在序列式數值常出現時能夠幫助壓縮大小。

  2. Cacheing:查詢語句(query)通常都是有熱點的,所以能夠把頻率高的query答案cache起來。

  3. Frequency-based optimization:並非List的每個document都返回,只返回top ten就行了。因此咱們能夠維護兩套inverted indices,一套是top ten的,常駐RAM;另外一套是剩下的,常駐硬盤。只要大部分的query都訪問top ten,那整個系統的throughput 和 latency都是能夠接受的。

  4. Intersection order:合併的順序對速度有很大影響,要先合併小集合。上面的例子,應先合併nirvana和grammy,而後拿合併的結果與beatles去合併,這樣速度最快。

咱們也能夠用多級索引(multilevel index)增長準確率。對於一個高優先級的web page,能夠先將其分解成paragraphs和sentences,而後把這些paragraphs和sentences拿去建索引。
這樣的好處是,咱們有可能在一個paragraph或者一個sentence裏找到query裏全部的詞,這是分級以前作不到的,以前咱們的粒度只能返回文檔(web page),如今能夠返回paragraph和sentence,精度提升。

算法:

這裏涉及兩個算法題:

  1. 找兩個sorted list的共同元素(intersection):兩個指針從頭比,一大一小不要小(advance小的pointer),兩個相等就添加到result,O(N+M)。

  2. find the smallest subarray covering all values(搜索的結果裏會給一段話,裏面highlight出這兩個關鍵字):兩個指針維護窗口,O(N)

8. Implement PageRank

場景:

設計一個能夠快速計算100億網頁pagerank的系統。

分析:

PageRank的介紹:Introduction to PageRank
簡單地說就是一個N(網頁數)維矩陣A,其中i行j列的值表示用戶從頁面j轉到頁面i的機率。
每一個網頁做爲一個vertex,有超連接的兩個網頁有一條邊,整張圖是一個稀疏圖,最好用鄰接表(adjacency list)來表示。
建表的過程通常是:下載網頁(pages),提取網頁的超連接。URL因爲長度變化很大,因此通常將其hash。
整個算法最耗時的地方在於矩陣乘法的迭代。
鄰接表用一臺電腦的RAM是放不下的,咱們通常有兩種作法:

  1. Disk-based sorting:

相關文章
相關標籤/搜索