回溯算法刷题套路
引言
回溯算法實際上就是對樹形或者圖形結構執行一次深度優先遍歷,實際上類似枚舉的搜索嘗試過程,在遍歷的過程中尋找問題的解,深搜實際上就是遞歸的過程,所以我們得明白幾個前提:
1,回溯類的題目可以轉化為N叉樹去做(一般畫圖就有思路了)
2,回溯類的題目實際就是一種暴力的深搜;
回溯函數中的for循環和遞歸的作用
回溯算法都有個套路,就是寫一個回溯函數backtracking,一般格式就是:
void backtracking(參數) {if (終止條件) {存放結果;return;}for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) {處理節點;backtracking(路徑,選擇列表); // 遞歸回溯,撤銷處理結果} }這里的for循環套遞歸分別是什么意思?
for循環是橫向遍歷N叉樹
遞歸是縱向遍歷N叉樹
所以只有當一縱列遞歸完了,for循環才會走到下一縱列;而且遞歸結果是逐漸從下往上來的;
問題類型:子集問題,組合問題,分割問題,排列問題
子集問題要求的其實就是樹的每一個節點,而組合和分割問題都是求的葉子節點,排列問題也是求葉子節點,但是和組合分割又有區別
子集問題(入門):78. 子集
組合問題(入門):77. 組合(回溯算法)
分割問題(入門):131. 分割回文串(回溯算法)
排列問題(入門):46.全排列
什么時候需要一個start變量作為for循環起點?
當通過一個集合求組合(或分割或子集),需要start,防止重復;
for (int i = start; i < nums.size(); ++i)題目:77. 組合(回溯算法)
當通過多個集合求組合(或分割或子集),不需要start,因為各個集合都不同不會相互影響;
題目:17. 電話號碼的字母組合(回溯算法)
如果是排列問題的話,就不需要start,因為每到新的一列都需要重新使用之前使用的元素;
for (int i = 0; i < nums.size(); ++i)如果有start,什么時候遞歸時i加1?
當一個集合中每個元素只能使用一次時,i + 1;(例題代碼)
for (int i = start; i < nums.size(); ++i) {path.push_back(nums[i]);backtracking(nums, i + 1);//遞歸 i + 1path.pop_back();//回溯}當一個集合中每個元素可以重復使用時,不用加1;
for (int i = start; i < nums.size(); ++i) {path.push_back(nums[i]);backtracking(nums, i);//遞歸 i不用加1path.pop_back();//回溯}例題:求組和數II
這里 i + 1 實際上就是在遞歸過程中縱向處理子串,使每向下走一層就減少一個之前使用過的元素,防止重復使用之前使用過的元素;(畫圖好理解,主要理解遞歸是縱向遍歷的就好理解了)
需要去重類的題目:“樹層去重”和“樹枝去重”
有的題目不讓結果出現重復的元素,所以需要一個去重操作;
一般是在backtracking函數參數中多加入一個 vector<bool> used型的參數,記錄每個節點的使用情況
這里需要注意去重有兩種情況:
數層去重和樹枝去重
used[i] == true,說明同一樹枝元素 i 使用過
used[i] == false,說明同一樹層元素 i 使用過
在子集和組合的題目中的去重實際上要去重的是同一樹層 上的“使用過”,同一樹枝上的都是一個組合里的元素,不用去重
而在排列的題目中去重去兩種情況都可能出現,但是樹枝去重是必須的,樹層去重需要根據實際情況來判斷;
如題目:47. 全排列 就是兩種去重都有的;
這里說明一下為什么是false,因為如果為false,說明遞歸已經結束,已經通過回溯回到了false,所以此時該遍歷的是旁邊一層(即下一個樹枝,同一層)的元素,即右移一個;
如果是true,則說明是這一個樹枝上已經使用的元素,一般這里去重出現在排列中;
例題 90.子集||部分代碼(樹層去重)
for (int i = start; i < nums.size(); ++i) {//去重操作if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) {continue;}path.push_back(nums[i]);used[i] = true;backtacking(nums, i + 1, used);used[i] = false;path.pop_back();}例題46.全排列部分代碼(樹枝去重)
for (int i = 0; i < nums.size(); ++i) {if (used[i] == true) continue;used[i] = true;path.push_back(nums[i]);backTracking(nums, used);path.pop_back();used[i] = false;}樹層去重前要注意需要先給原集合進行一個排序;
used初始化為false
例題:40.組合總和II
樹層去重的前提是要先對原集合進行排序;
這里也有特殊情況:419.遞增子序列
這個題目就不能排序,可以看看解析;
補充:什么時候需要 樹層去重 什么時候需要 樹枝去重
1,如果一個集合里面會出現重復的元素,結果還不能有重復,那么就需要樹層去重
2,樹枝去重一般就會出現在排列問題中,其他問題暫時還沒有遇到
總結
- 上一篇: 93. 复原 IP 地址(回溯算法)
- 下一篇: 491. 递增子序列(回溯算法)