DFS經(jīng)典問題解析

定義

Depth First Search (DFS): an algorithm for traversing or searching tree or graph data structures. The algorithm starts at the root node (selecting some arbitrary node as the root node in the case of a graph) and explores as far as possible along each branch before backtracking.

要點:樹或者圖搜索算法坐儿,回溯之前沿著一個方向explore AFAP.

DFS可以用遞歸(recursive) 或者迭代的方式實現(xiàn)闰围。recursive method的general 實現(xiàn)邏輯如下:

/***
input: 圖和圖中一個節(jié)點
output:  v可以達到的所有節(jié)點被labeled as discovered.
***/
void DFS(graph, v){
      label v as discovered
      for all directed edges from v to w in G.adjacentEdges(v) 
         if vertex w is not labeled as discovered:
               DFS(G,w)
}

時間復(fù)雜度為O(n). 空間復(fù)雜度為調(diào)用棧的深度worst case is O(|E|)店雅。|E| 為edge的個數(shù)淹魄。

iterative 的實現(xiàn)方式如下, 空間復(fù)雜度worst-case同樣為O(|E|):

void DFS-iterative(graph, v){
      let S be a stack
      S.push(v)
      whille(!S.isEmpty()){
             v = S.pop()
             label v as discovered
             for all edges from v to w in G.adjacentEdges(v){
                  S.push(w)
             }
      }
}

DFS的遞歸實現(xiàn)和迭代實現(xiàn)糖荒,在現(xiàn)實生活中都有應(yīng)用,各有優(yōu)缺點围来,取決于具體應(yīng)用場景:

Recursive: 實現(xiàn)簡單刺桃,容易理解,在調(diào)用棧層數(shù)不多(Java將每層的方法調(diào)用(frame)存在方法棧當中桶蛔,而方法棧的內(nèi)存遠小于heap)的情況下匙头,可以使用。但實際生產(chǎn)中仔雷,圖大多比較龐大復(fù)雜蹂析,比如朋友圈,Google的網(wǎng)頁碟婆,交通路線圖等电抚,我們通常避免使用遞歸調(diào)用,容易產(chǎn)生StackOverflow的問題竖共。
Iterative: 使用stack數(shù)據(jù)結(jié)構(gòu)(Java會將這個棧分配在heap中)蝙叛,保存回溯路徑。相比于Recursive實現(xiàn)復(fù)雜些公给,不容易直接想到借帘。在算法面試或者競賽中,通常不會使用淌铐。

本文主要圍繞DFS肺然,來講解四道經(jīng)典的面試算法題,盡管Leetcode上有200多道DFS類相關(guān)的題型腿准,但掌握了本文的四道算法題思路际起,解決其他問題基本上沒啥問題。

Note: 不要將DFS的Iterative實現(xiàn)和BFS混淆吐葱,盡管兩者都是非遞歸調(diào)用實現(xiàn)街望,但是explore圖的方向不同,BFS講究由里到外的"地毯式搜索"弟跑,而DFS則會沿著一條路徑走到黑它匕,然后回溯。

子集合問題

題目要求print給定集合所有的subset窖认,比如given input = {1, 2, 3}豫柬,那么expected輸出應(yīng)該為{{}, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}}。

解題思路:打印出給定集合的所有子集本質(zhì)上是一道數(shù)學題扑浸,給定集合每個元素都是相互獨立的烧给,它可以出現(xiàn)或者不出現(xiàn)在子集中,那么現(xiàn)在有3個元素喝噪,最中就會有2^3 = 8 種組合础嫡。想清楚這點之后,接下來就可以考慮構(gòu)建遞歸樹酝惧,構(gòu)建遞歸樹之前呢榴鼎,我們需要問自己兩個問題:

  1. 遞歸樹每層代表什么含義?
    Ans: 每層樹表示的含義是對于每個元素是選取還是不選取晚唇,那么遞歸樹一共有3層
  2. 每層有多少狀態(tài)巫财?
    Ans: 每層有兩個狀態(tài)(選擇或者不選擇),那么三層樹哩陕,最終會有8個狀態(tài)平项,總體的時間復(fù)雜度為(2^n), n為input的size悍及。

想清楚上面兩個問題就可以畫出如下的遞歸樹

圖中每個中間節(jié)點都是子集結(jié)果的臨時狀態(tài)闽瓢,而所有的葉子節(jié)點就是給定集合的子集。

OK心赶,搞定了遞歸樹的問題扣讼,代碼就很好寫了,如下所示


    /**
     * @param set: 給定的輸入集合[1, 2, 3]
     * @param level: 當前l(fā)evel層數(shù)缨叫,表示visit 第level個元素
     * @param temp:用于保存當前臨時結(jié)果集合
     * @param result: 用于保存結(jié)果的集合
     */
void findSubset(int[] set, int level, List<Integer> temp, List<List<Integer>> result){
         //遞歸到最后一層葉子節(jié)點
         if(level == set.length){
                 //將臨時結(jié)果保存到最終結(jié)果集中
                 result.add(new ArrayList(temp));
                 return;
          }
          //選擇加入當前元素, 然后繼續(xù)向下遞歸
          temp.add(set[level]);
          findSubset(set, level + 1, temp, result);
          //選擇不加入當前元素椭符,然后繼續(xù)向下遞歸
          temp.removeLast();
          findSubset(set, level + 1, temp, result);
}

相似的題型包括LC494 Target Sum, LC491 Increasing Subsequences,

硬幣組合問題

給定一組硬幣弯汰,根據(jù)已有的25, 5, 1 美分的硬幣艰山,求出所有的硬幣組合湊出給定的目標金額,比如40美分咏闪。其中每種硬幣的個數(shù)不限曙搬。這類硬幣組合問題在面試中可謂是老生常談了,比如LC518, LC322鸽嫂,LC494等等纵装。

如上題一樣,首先來畫遞歸樹据某,兩個問題

  1. 遞歸樹每層代表什么含義橡娄?
    Ans: 每層代表使用一種類型的硬幣比如第一層只使用25美分的硬幣,有三種類型的硬幣癣籽,那么遞歸樹就有三層
  2. 每層有多少狀態(tài)挽唉?
    Ans: 每層的狀態(tài)個數(shù)取決于當前的硬幣的類型滤祖,比如第一層目標金額為40, 選擇使用25美分的硬幣瓶籽,就有40/25 + 1 (選擇0個25沒分) = 2 種狀態(tài)匠童。遞歸樹節(jié)點最多有40狀態(tài)(選擇40 個 1 美分的硬幣,0 個25美分塑顺,0個5美分)汤求,那么時間復(fù)雜度就為O(40^3)。

接下來的就是代碼實現(xiàn)了

 /*** 
 * @param moneyLeft: 剩下錢的數(shù)量
 * @param level: 當前使用的硬幣
 * @param coins: 硬幣種類集合, 
 * @param sol: 硬幣個數(shù), sol[i]表示使用coins[i]的個數(shù)
 */
void findCombination(int moneyLeft, int level, int[] coins, int[] sol) {
     If (level == coins.length - 1){
        sol[level] = moneyLeft; //假設(shè)coins[3] = 1严拒,這一點要和面試官確認
        print(coins, sol); //打印結(jié)果
        return
     }
    for (int i = 0; i * coins[level] <= moneyLeft; i++){
        sol[level] = i; //使用coins[level]的個數(shù)
        findCombination(moneyLeft -  i * coins[level] , level  + 1, coins, sol );
    }
}

private String print(int[] coins, int[] sol) {
     for(int i = 0; i < coins.length; i++){
          System.out.println("使用 {} 個 {} 美分硬幣".format(sol[i], coins[i]));
     }
}

解決本題還有另外一種思路扬绪,給定三種硬幣 25, 5, 1,那么遞歸樹每一層其實有三種選擇裤唠,要么選擇1個25美分挤牛,要么選擇1個5美分,再要么選擇1個1美分巧骚,那么遞歸樹就會有99層赊颠,每層有3個選擇,時間復(fù)雜度則為O(3^99)劈彪。如果給定一個很大的目標金額竣蹦,那么很容易出現(xiàn)StackOverflow。

排列問題

Print all valid permutations of ()()()

valid case: whenever we add (, it is always valid;

              Whenever we add ), we must guarantee number of the left parenthesis added so far > right parenthesis added so far.

What does it stores on each level ? (每層表什么含義)

Six levels, for each level, it makes the decision whether to put left or right parenthesis on each position.

How many different stages should we try to put on this level ? (每層有多少個狀態(tài))

兩個狀態(tài). Add Left parenthesis or right parenthesis

[圖片上傳失敗...(image-7c71eb-1573464603037)]

正在上傳...[取消](javascript:void(0))

void DFS(int left, int right, String temp, List<String> result){

if (left == n && right == n){

                  result.add(temp.toString());

           }

           if (left < n){

              DFS(left + 1, right, temp + “(”,  result);

          }

          if(right < left){

                  DFS(left, right + 1, temp + “)”,  result);

           }

}

DFS絕大部分問題的解題思路是枚舉出所有的組合沧奴,然后根據(jù)問題要求過濾出符合條件的答案痘括。

補充說明

畫遞歸樹主要幫助我們分析時間復(fù)雜度。

Print all valid permutations of ()()() 左括號滔吠,右括號

湊硬幣金額根據(jù)已有的 1, 5, 25分coin纲菌, 打印出所有的組合,99金幣

Given a String, and output all permutation ------->switch 來, switch去

Permutation of a String with (without) duplication

Permutation of all subsequences of a sorted string (allow a set contains duplicate string)

Permutation of factors of number

Levels

生成隨機的maze

重建行程單疮绷,給定一組行程列表 list of <start, end>翰舌,求訪問node的順序,如果多個path 存在冬骚,取lexical order 較小的那個

Tree的Traversal, pre-order, in-order and post-order

例題4. Given a String, and output all permutation

For example, s = “abc”

DFS基本分析法

What does it stores on each level ? (每層表什么含義)

3, each level represents each position, the character we can choose

How many different stages should we try to put on this level ? (每層有多少個狀態(tài))

第一個postion, we have 3 choice, 第二個postion, we have 2 choice, 第三個只有一個

時間復(fù)雜度取決于數(shù)最后一層節(jié)點的個數(shù)O(n!)

[圖片上傳失敗...(image-745401-1573464603037)]

正在上傳...[取消](javascript:void(0))

Both BFS and DFS can solve permutation issue, but we don’t use BFS, since BFS may have very large queue size, due to recursive tree grows exponentially. OOM

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末椅贱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子只冻,更是在濱河造成了極大的恐慌庇麦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喜德,死亡現(xiàn)場離奇詭異山橄,居然都是意外死亡,警方通過查閱死者的電腦和手機舍悯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門航棱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來睡雇,“玉大人,你說我怎么就攤上這事饮醇∪牍穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵驳阎,是天一觀的道長。 經(jīng)常有香客問我馁蒂,道長呵晚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任沫屡,我火速辦了婚禮饵隙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沮脖。我一直安慰自己金矛,他們只是感情好,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布勺届。 她就那樣靜靜地躺著驶俊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪免姿。 梳的紋絲不亂的頭發(fā)上饼酿,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音胚膊,去河邊找鬼故俐。 笑死,一個胖子當著我的面吹牛紊婉,可吹牛的內(nèi)容都是我干的药版。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喻犁,長吁一口氣:“原來是場噩夢啊……” “哼槽片!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起株汉,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筐乳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乔妈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蝙云,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年路召,在試婚紗的時候發(fā)現(xiàn)自己被綠了勃刨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片波材。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖身隐,靈堂內(nèi)的尸體忽然破棺而出廷区,到底是詐尸還是另有隱情,我是刑警寧澤贾铝,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布隙轻,位于F島的核電站,受9級特大地震影響垢揩,放射性物質(zhì)發(fā)生泄漏玖绿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一叁巨、第九天 我趴在偏房一處隱蔽的房頂上張望斑匪。 院中可真熱鬧,春花似錦锋勺、人聲如沸蚀瘸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贮勃。三九已至,卻和暖如春悬包,著一層夾襖步出監(jiān)牢的瞬間衙猪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工布近, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留垫释,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓撑瞧,卻偏偏與公主長得像棵譬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子预伺,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容