52. N-Queens II 允跑,全排列類型DFS的理解

Jun 23 更新
昨天看sudoku solver又陷入強烈的疑惑中键俱,為什么所有人的dfs函數(shù)都是boolean的返回值兰绣??

今天回顧了一下之前知乎的回答编振,怎么讓N QUEENS在找到一個解后就跳出循環(huán)缀辩?其實之前理解得不夠深。今天又仔細跟了一下踪央,發(fā)現(xiàn):

情形一:void返回值

  1. ** 一次return只能跳出一層遞歸臀玄。**
    下面的圖,為什么4 QUEENS的情形第一次是在第二層就停止畅蹂,不會進入第三層健无?因為,在進入第三層之前調用了四次checkValid()發(fā)現(xiàn)第三層都不滿足液斜,然后就會繼續(xù)往下走了累贤,也就是走出for循環(huán),程序結束少漆,也就是隱含的return void臼膏,回到上一層繼續(xù)for循環(huán)。只跳出了一層遞歸示损。
  2. 找到一個解{1, 3, 0, 2}之后渗磅,加到解集里面去,然后return,會發(fā)生什么始鱼?
            if (checkValid(row, col)) {
                dfs(result, row + 1, n, col);
            }

row 原本等于4了仔掸,但回來之后回到進去下一層之前的狀態(tài),row = 3医清,再次for循環(huán)起暮,不滿足, col: {1, 3, 0, 3}状勤。

  • 這時候鞋怀,函數(shù)執(zhí)行完了双泪,相當于return到上一層持搜,row = 2。

  • 繼續(xù)for循環(huán)發(fā)現(xiàn)焙矛,row = 2的后三個位置也不行了葫盼,又執(zhí)行完了,return的上一層村斟,row = 1贫导。

  • row = 1的i本來就在第四個位置了,回到上一層蟆盹,row = 0.

  • 那因為row = 0 的時候我們checkValid一定是true孩灯,所以就進入下一層DFS,row = 1逾滥。這時候會觸發(fā)我們加的if (result.size() > 0) return;回到上一層峰档。剛才i是等于2的,這時候就繼續(xù)吧寨昙,i = 3讥巡。然后往復進入下一層再回來,i = 4舔哪,也就是說在它進入row = 1的for循環(huán)之前讓它回到row = 1欢顷。那i = 4函數(shù)就執(zhí)行完了。這已經是最頂層的夢了捉蚤,也就真的醒來了抬驴。

也就是說,只有在回溯到了第一層缆巧,有機會進入下一層的時候才能讓它一步步return布持,不然它又往第二層第三層去找了。

這種方法對應的是把if (result.size() > 0) return;加到if (row == n) {
...
result.add(new ArrayList<String>(cell));
return;
}
后面的情況盅蝗,所以還要再找一遍234層鳖链,最后回到第一層的時候,col變成了{1,3,3,3},最后在從1層下到2層的時候才能一步步return芙委,那優(yōu)化一下逞敷,把這個return放到for循環(huán)里面:

        for (int i = 0; i < n; i++) {
            if (result.size() > 0)
                return;
            col[row] = i;
            if (checkValid(row, col)) {
                dfs(result, row + 1, n, col);
            }
        }

這樣的話在backtracking的時候就不會再找1303這樣的數(shù)了,在{1,3,0,2}的狀態(tài)下就return了(也就是說都不用全局變量保存了灌侣,直接讀取這個數(shù)組的內容就行了)推捐。

情形二(重點):boolean 遞歸中的if(dfs()){ return true;}的意義

剛才我又看了一遍,boolean類型的dfs函數(shù)真正的意義侧啼,其實是..

  public boolean dfs(List<List<String>> result, int row, int n, int[] col) {
        if (row == n) {
            //保存解到解集
            //...
            return true;
        }
        for (int i = 0; i < n; i++) {
            col[row] = i;
            if (checkValid(row, col)) {
                if (dfs(result, row + 1, n, col)) return true;
            }
        }
        return false;
    }

這時候如果找到一個解牛柒,return true的話,回到了上一層痊乾,上一層的入口的返回值也就成了true皮壁,這時候連for循環(huán)都不用走,直接就一層層退出了D纳蟆6昶恰!

這里的也不一定是boolean湿滓,用某個int值也行滴须。

對于sudoku solver:

sudoku solver那題題目說,You may assume that there will be only one unique solution. 那我如果不及時讓他停下叽奥,他必然會找到解后又繼續(xù)找扔水,最后回到原始狀態(tài)(為什么是最原始的狀態(tài)呢)。
那我能想到讓它變成void的方法就是找到一個解后保存下來朝氓,然后隨他去吧魔市,恢復了也無所謂;當然也可以在for循環(huán)第一句里寫上:if (res != null) return; 但是這題不是要return一個結果膀篮,而是直接對傳進來的board進行操作嘹狞,但我最后把board賦值回res,發(fā)現(xiàn)不行誓竿,就先不搞了磅网。

1:11 AM 24/06/2017


原po:


盜夢.jpg

上面這張圖是我思考的全排列類型DFS的運行過程,這種DFS的特點就是for循環(huán)里面有遞歸筷屡,舉個N Queens的例子:

        for (int i = 0; i < n; i++) {
            //i表示Q所在的列 從頭到尾遍歷一遍
            col[row] = i;
            if (checkValid(row, col)) {
                dfs(result, row + 1, n, col);
            }
        }

一開始理解這種題的時候我完全不懂涧偷,現(xiàn)在懂一些了。原因就是這道N QueensII毙死,因為這道題求解的是Queens的個數(shù)燎潮,我就在想,這個遞歸是找到一個答案就結束還是找到所有答案再結束呢扼倘?因為終止條件是row == n的時候return确封,但是return了之后recursion并沒有結束除呵,因為經過了for循環(huán)。如圖爪喘,其實對于每一個row颜曾,for循環(huán)都要從1到n-1走一遍,如果valid就進行到下一層的dfs里去秉剑,那么:

什么時候會backtracking(回溯)泛豪?

  1. 找到第一個解之后return的時候會backtracking。
    第一張圖里的row==1的時候侦鹏,Q在第三格發(fā)現(xiàn)后面checkVaild都失效了诡曙,就移動到第四格;然后走走走略水,到第二張圖的情形row == n找到一個解return的時候价卤,這時候row回到row - 1的狀態(tài),也就是上一層的夢境(第二張圖)聚请,彼時i = 2荠雕,那么i這時候繼續(xù)走,i= 3 了驶赏,checkValid失效,這時候遇到第二種:
  2. row = 3的for循環(huán)走完了的時候既鞠。
    這時候發(fā)現(xiàn)棧中還有上一層的遞歸沒有執(zhí)行完煤傍,上一層的夢醒了,繼續(xù)做嘱蛋。也就是把row =2的Q移動到第二個格子蚯姆。

感謝盜夢空間。

怎么判斷是什么時候回溯洒敏?用IDE調試著看龄恋。
我還有個問題,怎么讓NQUEENS找到一個解就停止凶伙?

關于怎么讓NQUEENS找到一個解就停止郭毕,我在斗魚上提問了,覃超立刻回應了函荣,就是加個終止條件显押。我加了這么一句就可以了:

if (result.size()>0) return;

對應于上面的圖二,就是每次回到上一層的夢傻挂,就發(fā)現(xiàn)這一層已經不滿足了乘碑,所以一直回到第一層的夢,最后醒來金拒。

參數(shù)「歸去來兮」的問題

也就是還原兽肤,恢復現(xiàn)場。這題的debug:

1303.png

就在圖中這一步之前,col一直是[1,3,0,2]的狀態(tài)资铡,沒有因為return就回到之前的[1,3,0,0]的狀態(tài)沉迹。但這題很特殊,因為第四個格子的數(shù)字2反正會被覆蓋所以不用還原害驹。

我終于理解了1夼弧!intellij idea的左下角顯示的是函數(shù)棧

左下角是函數(shù)棧

一直以來我不太理解什么時候歸去來兮宛官,現(xiàn)在終于好像開竅了 葫松。上面這題是Generate Paratheses的遞歸,我觀察到IDEA的左下角底洗,竟然有9個dfs函數(shù)腋么,按照9~1排列,點開之后亥揖,我發(fā)現(xiàn)右邊的variables grid里顯示的是每一個dfs的狀態(tài)珊擂!太神奇了。费变。哈哈摧扇,這里面的StringBuilder就不斷地在變化,第1個是""挚歧,第9個就是圖中的"(((())))"扛稽。
然后,找到一個解之后return的時候滑负,回到dfs stack 8在张,結果8執(zhí)行完了沒有執(zhí)行stack7而是直接執(zhí)行了stack4,sb變成了"((("矮慕,然后執(zhí)行5帮匾,變成"((()"。這時候遞歸已經不太好理解了痴鳄,大概就是因為left對應著多個right瘟斜,才導致left4的4個right執(zhí)行完了就開始執(zhí)行l(wèi)eft3的right們。我就不去深究了夏跷。重要的事哼转,這里為什么不需要還原sb的問題,如果寫成:

        if (right < left) {
            sb.append((")"));
            dfs(left, right + 1, result, sb, n);
            sb.deleteCharAt(sb.length() - 1);
        }

這樣是要還原的槽华,因為這里sb沒有被當做參數(shù)傳到下一層遞歸里面去壹蔓,只是sb的指針(也就是內存地址)傳到dfs下一層去了,所以要手動還原猫态,同樣的道理佣蓉,52題的N-QUEENSII也是這樣披摄,左邊的棧中我們會看到每個棧的col里面都是一樣的值,這是因為col沒有在參數(shù)中被改變勇凭,而是作為一個「全局」變量在使用疚膊,內存地址沒有改變。
而Generate Parentheses這題就不一樣虾标,回到上一層的夢境的時候寓盗,sb已經回到之前的狀態(tài)了。

覃超對于word search那道題不需要還原的解釋:

參數(shù)的時候不要歸去來兮 值拷貝的
drunkpiano 22:48:46
定義在參數(shù)里面璧函,并且參數(shù)做了值拷貝傀蚌,就幫你還原了
drunkpiano 22:49:01

傳遞到board里面 只是把指針傳進去了 所以要手動還原
drunkpiano 22:49:14
currentWord值拷貝了,不用手動還原

再來理解一下蘸吓,為什么對于遞歸參數(shù)中的i,j這些index的值不需要還原善炫,因為它們是基本數(shù)據類型啊,本身就沒有引用库继,也就是說箩艺,只有在遞歸的參數(shù)是指向原來的某個引用的指針的時候,才需要還原宪萄。
這也是覃超為什么說Generate Parentheses 那題用StringBuilder雖然需要恢復現(xiàn)場艺谆,但好處是不會每次都創(chuàng)建新的對象。

今天弄懂了這些還是挺開心的雨膨。感覺對dfs的理解加深了不少擂涛。想起MK書里寫的,你做得不好不代表你不能做啊聊记。。做得慢不代表你不會做啊恢暖。排监。慢慢來吧。

對了杰捂,差點忘了貼一下我盲寫的這題的AC代碼,注意checkValid那邊舆床。

public class Solution {
    int num = 0;

    public int totalNQueens(int n) {
        int[] col = new int[n];
        dfsHelper(n, col, 0);
        return num;
    }

    private void dfsHelper(int n, int[] col, int row) {
        if (row == n) {
            num++;
            return;
        }

        for (int i = 0; i < n; i++) {
            col[row] = i;
            if (checkValid(row, col)) {
                dfsHelper(n, col, row + 1);
            }
        }
    }

    private boolean checkValid(int row, int[] col) {
        for (int i = 0; i < row; i++)
//            for (int j = i + 1; j < row; j++) {
            //不要用double for cycle,因為row之前的已經valid了不需要比對了嫁佳,只需要把每個i跟row比
            if (Math.abs(i - row) == Math.abs(col[i] - col[row]) || col[i] == col[row]) {
                return false;
//                }
            }
        return true;
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末挨队,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蒿往,更是在濱河造成了極大的恐慌盛垦,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓤漏,死亡現(xiàn)場離奇詭異腾夯,居然都是意外死亡颊埃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門蝶俱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來班利,“玉大人,你說我怎么就攤上這事榨呆÷薇辏” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵积蜻,是天一觀的道長闯割。 經常有香客問我,道長浅侨,這世上最難降的妖魔是什么纽谒? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮如输,結果婚禮上鼓黔,老公的妹妹穿的比我還像新娘。我一直安慰自己不见,他們只是感情好澳化,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稳吮,像睡著了一般缎谷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灶似,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天列林,我揣著相機與錄音,去河邊找鬼酪惭。 笑死希痴,一個胖子當著我的面吹牛,可吹牛的內容都是我干的春感。 我是一名探鬼主播砌创,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鲫懒!你這毒婦竟也來了嫩实?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤窥岩,失蹤者是張志新(化名)和其女友劉穎甲献,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谦秧,經...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡竟纳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年撵溃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锥累。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡缘挑,死狀恐怖,靈堂內的尸體忽然破棺而出桶略,到底是詐尸還是另有隱情语淘,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布际歼,位于F島的核電站惶翻,受9級特大地震影響,放射性物質發(fā)生泄漏鹅心。R本人自食惡果不足惜吕粗,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旭愧。 院中可真熱鬧颅筋,春花似錦、人聲如沸输枯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桃熄。三九已至先口,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞳收,已是汗流浹背碉京。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留螟深,地道東北人收夸。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像血崭,于是被迫代替她去往敵國和親厘灼。 傳聞我的和親對象是個殘疾皇子夹纫,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內容