回溯法排序樹怎麼畫 回溯演算法 求組合問題!

2021-10-16 08:47:23 字數 4326 閱讀 9893

回溯法的第一道題目,就不簡單呀!

❞給定兩個整數 n 和 k,返回 1 ... n 中所有可能的 k 個數的組合。

示例:輸入: n = 4, k = 2

輸出:[

[2,4],

[3,4],

[2,3],

[1,2],

[1,3],

[1,4],

] 本題這是回溯法的經典題目。

直接的解法當然是使用for迴圈,例如示例中k為2,很容易想到 用兩個for迴圈,這樣就可以輸出 和示例中一樣的結果。

**如下:

int n = 4;

for (int i = 1; i <= n; i++) 

}

輸入:n = 100, k = 3 那麼就三層for迴圈,**如下:

int n = 100;

for (int i = 1; i <= n; i++)     }}

「如果n為100,k為50呢,那就50層for迴圈,是不是開始窒息」

「此時就會發現雖然想暴力搜尋,但是用for迴圈巢狀連暴力都寫不出來!」

咋整?回溯搜尋法來了,雖然回溯法也是暴力,但至少能寫出來,不像for迴圈巢狀k層讓人絕望。

那麼回溯法怎麼暴力搜呢?

上面我們說了「要解決 n為100,k為50的情況,暴力寫法需要巢狀50層for迴圈,那麼回溯法就用遞迴來解決巢狀層數的問題」

遞迴來做層疊巢狀(可以理解是開k層for迴圈),「每一次的遞迴中巢狀乙個for迴圈,那麼遞迴就可以用於解決多層巢狀迴圈的問題了」

一些同學本來對遞迴就懵,回溯法中遞迴還要巢狀for迴圈,可能就直接暈倒了!

如果腦洞模擬回溯搜尋的過程,絕對可以讓人窒息,所以需要抽象圖形結構來進一步理解。

「我們在關於回溯演算法,你該了解這些!中說道回溯法解決的問題都可以抽象為樹形結構(n叉樹),用樹形結構來理解回溯就容易多了」

那麼我把組合問題抽象為如下樹形結構:

可以看出這個棵樹,一開始集合是 1,2,3,4, 從左向右取數,取過的數,不在重複取。

第一次取1,集合變為2,3,4 ,因為k為2,我們只需要再取乙個數就可以了,分別取2,3,4,得到集合[1,2] [1,3] [1,4],以此類推。

「每次從集合中選取元素,可選擇的範圍隨著選擇的進行而收縮,調整可選擇的範圍」

「圖中可以發現n相當於樹的寬度,k相當於樹的深度」

那麼如何在這個樹上遍歷,然後收集到我們要的結果集呢?

「圖中每次搜尋到了葉子節點,我們就找到了乙個結果」

相當於只需要把達到葉子節點的結果收集起來,就可以求得 n個數中k個數的組合集合。

在關於回溯演算法,你該了解這些!中我們提到了回溯法三部曲,那麼我們按照回溯法三部曲開始正式講解**了。

在這裡要定義兩個全域性變數,乙個用來存放符合條件單一結果,乙個用來存放符合條件結果的集合。

**如下:

vector> result; // 存放符合條件結果的集合

vector path; // 用來存放符合條件結果

其實不定義這兩個全域性遍歷也是可以的,把這兩個變數放進遞迴函式的引數裡,但函式裡引數太多影響可讀性,所以我定義全域性變數了。

函式裡一定有兩個引數,既然是集合n裡面取k的數,那麼n和k是兩個int型的引數。

然後還需要乙個引數,為int型變數startindex,這個引數用來記錄本層遞迴的中,集合從**開始遍歷(集合就是[1,...,n] )。

為什麼要有這個startindex呢?

「每次從集合中選取元素,可選擇的範圍隨著選擇的進行而收縮,調整可選擇的範圍,就是要靠startindex」

從下圖中紅線部分可以看出,在集合[1,2,3,4]取1之後,下一層遞迴,就要在[2,3,4]中取數了,那麼下一層遞迴如何知道從[2,3,4]中取數呢,靠的就是startindex。

所以需要startindex來記錄下一層遞迴,搜尋的起始位置。

那麼整體**如下:

vector> result; // 存放符合條件結果的集合

vector path; // 用來存放符合條件單一結果

void backtracking(int n, int k, int startindex)

什麼時候到達所謂的葉子節點了呢?

path這個陣列的大小如果達到k,說明我們找到了乙個子集大小為k的組合了,在圖中path存的就是根節點到葉子節點的路徑。

如圖紅色部分:

此時用result二維陣列,把path儲存起來,並終止本層遞迴。

所以終止條件**如下:

if (path.size() == k) 

回溯法的搜尋過程就是乙個樹型結構的遍歷過程,在如下圖中,可以看出for迴圈用來橫向遍歷,遞迴的過程是縱向遍歷。

如此我們才遍歷完圖中的這棵樹。

for迴圈每次從startindex開始遍歷,然後用path儲存取到的節點i。

**如下:

for (int i = startindex; i <= n; i++) 

可以看出backtracking(遞迴函式)通過不斷呼叫自己一直往深處遍歷,總會遇到葉子節點,遇到了葉子節點就要返回。

backtracking的下面部分就是回溯的操作了,撤銷本次處理的結果。

關鍵地方都講完了,組合問題c++完整**如下:

class solution for (int i = startindex; i <= n; i++) 

}public:

vector> combine(int n, int k) 

};

還記得我們在關於回溯演算法,你該了解這些!中給出的回溯法模板麼?

如下:

void backtracking(引數) 

for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) 

}

「對比一下本題的**,是不是發現有點像!」所以有了這個模板,就有解題的大體方向,不至於毫無頭緒。

組合問題是回溯法解決的經典問題,我們開始的時候給大家列舉乙個很形象的例子,就是n為100,k為50的話,直接想法就需要50層for迴圈。

從而引出了回溯法就是解決這種k層for迴圈巢狀的問題。

然後進一步把回溯法的搜尋過程抽象為樹形結構,可以直觀的看出搜尋的過程。

接著用回溯法三部曲,逐步分析了函式引數、終止條件和單層搜尋的過程。

「就醬,如果對你有幫助,就幫carl**一下吧,讓更多的同學發現這裡!」

-------end-------

我將演算法學習相關的資料已經整理到了github :裡面還有leetcode刷題攻略、各個型別經典題目刷題順序、思維導圖看一看一定會有所收穫,如果給你有幫助給乙個star支援一下吧!

趕緊給「**隨想錄」加乙個星標吧,方便第一時間閱讀文章往期

精彩回顧關於回溯演算法,你該了解這些!二叉樹:總結篇!雙指標法:總結篇!棧與佇列:總結篇!字串:總結篇!陣列:總結篇

「**隨想錄」期待你的關注!

每天8:35準時推送一道經典演算法題目,推送的每道題目都不是孤立的,而是由淺入深,環環相扣,幫你梳理演算法知識脈絡,輕鬆學演算法!

C 回溯法 剪枝 求組合數之和

已知一組數 其中有重複元素 求這組數可以組成的所有子集中,子集中的各個元素和為整數target的子集,結果中無重複的子集。例如 nums 10,1,2,7,6,1,5 target 8 結果為 1,7 1,2,5 2,6 1,1,6 該題無論是回溯法或位運演算法,整體時間複雜度為o 2 n 為了減小...

求自然數的組合數的回溯演算法

自然數的組合數處理標頭檔案 組合數的回溯演算法 內容 完成組合數的回溯演算法 內容 完成二叉樹的前,中序遍歷 非遞迴 內容 完成查詢二叉樹的靜,動態查詢 非遞迴 include stdlib.h define maxn 100 int number maxn 自然數的組合數回溯演算法comb bac...

求自然數的組合數的回溯演算法

自然數的組合數處理標頭檔案 組合數的回溯演算法 內容 完成組合數的回溯演算法 內容 完成二叉樹的前,中序遍歷 非遞迴 內容 完成查詢二叉樹的靜,動態查詢 非遞迴 include stdlib.h define maxn 100 int number maxn 自然數的組合數回溯演算法comb bac...