【算法】藍橋杯dfs深度優先搜索之排列組合總結


  為了重申感謝之意,再次聲明下文的大部分靈感均來自於【CSDN】梅森上校《JAVA版本:DFS算法題解兩個例子(走迷宮和求排列組合數)》
  強烈大家去上面那篇文章看看,寫的很好。
  下面我會列出藍橋杯第六屆B組省賽第7題、第七屆第5題、第八屆第4題,共3道題。

  因為他們都是:排列組合。

【第一道題】

牌型種數
  這道題可以強制轉為昨天的“湊算式”類型。
  首先,強調一下題意,總共13種牌A到K,每種可以選0到4張,總共選出13張,兩個13如果簡單表示的話就是2 13,其中13也可以用大寫的字母B表示,隱晦的透露了這道題的內涵。
  如果你還能想起來昨天“湊算式”的思路的話,那么上來第一件事肯定就是設置一個數組了
  下圖是我昨天在最后一題做的總結,對於這道題來說,也適合。
步驟
  第一件事,顯然這個數組的長度為13,因為我們要存13種牌,數組中只存0到4之間的數。

public static int[] a = new int[13]; 

  第二件事,這里不涉及到數字重用與否,略過。
  第三件事,定義dfs方法,還是和昨天一樣,就傳一個index參數

public static void dfs(int index) 

  第四件事,寫遞歸結束條件,這里就是index == 13,越界,代表A到K我們已經取完了,接下來就是要統計一下總數是不是13張。如果是的話,就算一種,count++。

// 遞歸結束條件 if(index == 13) { int sum = 0; for(int i : a) { sum += i; } if(sum == 13) { count++; } return; //遞歸結束一定要有return啊,沒有return不叫遞歸結束 } 

  第五件事,還未湊齊,深搜。a[]數組總共13個位置,每個位置是0到4中的一個數。代碼如下:

// 搜索 for(int i=0; i<=4; i++) { a[index] = i; dfs(index+1); } 

【完整代碼】

 1 public class 牌型種數dfs {
 2     public static int count = 0 ;
 3     public static int[] a = new int[13];
 4     public static void dfs(int index) {
 5         if(index == 13) {
 6             int sum = 0;
 7             for(int i : a) {
 8                 sum += i;
 9             }
10             if(sum == 13) {
11                 count++;
12             }
13             return;
14         }
15         // 搜索
16         for(int i=0; i<=4; i++) {
17             a[index] = i;
18             dfs(index+1); 
19         }
20     }
21 
22     public static void main(String[] args) {
23         dfs(0);
24         System.out.println(count); // 答案是: 3598180
25     }
26 
27 }

 

  其實我的這種解法,關鍵就在於對數組的使用是否熟練,用13個位置代表13個種類,每個位置只能填0到4,最后數組湊填滿后,統計一下每個位置之和是否是13。
  如果你每天吃飯、睡覺、聊天都是討論的和數組呀,dfs呀相關的,再加上看我寫的文章,照着代碼敲敲,那么用不了1天,准能掌握這種套路。

  這篇文章的標題是關於排列組合的,之所以開個新坑,就是想告訴大家,雖然我總結的步驟對大多數dfs類型的題有用,但是不要局限以為只有那樣的模式才算是dfs。
  比如同樣是這道題,同樣是dfs算法,但是代碼卻不一樣。下面的代碼參考自【CSDN】h1021456873《藍橋杯 牌型種數 (暴力||dfs)》

 1 public static int count = 0 ;
 2 public static void dfs(int type, int sum) {
 3     // 結束條件
 4     if(type == 13) { // A到K  13類
 5         if(sum == 13) { // 要湊夠13張
 6             count++;
 7         }
 8         return;
 9     }
10     // 搜索
11     for(int i=0; i<=4; i++) {
12         dfs(type+1, sum+i); // 此解法的關鍵,就在於sum+i 而不是sum+1
13     }
14 }
15 
16 public static void main(String[] args) {
17     dfs(0,0);
18     System.out.println(count);
19 }

 

  可以看到這個dfs方法傳入了兩個參數,上面的代碼沒有像我那樣使用數組,如果看懂我的代碼,這個也挺好理解的。
  之所以要說上面的代碼是要引出來下面這道題

【第二道題】

第二道題
  這是一道填空題,給出的代碼如下,其中的注釋是我添加的

 1 public class 抽簽dfs {
 2     
 3     public static void f(int[] a, int k, int n, String s) {
 4         // 結束條件
 5         if (k == a.length) {
 6             if (n == 0)
 7                 System.out.println(s);
 8             return;
 9         }
10         // 搜索
11         String s2 = s;
12         for (int i = 0; i <= a[k]; i++) {
13             _________________________// 填空位置
14             s2 += (char) (k + 'A');
15         }
16     }
17 
18     public static void main(String[] args) {
19         int[] a = { 4, 2, 2, 1, 1, 3 };
20         f(a, 0, 5, "");
21     }
22 }

 

  我還清楚的記得我第一次做這道題,當時我還不知道什么是dfs深度優先搜索,壓根沒看出來這代碼什么意思,只是覺得應該遞歸。經過上篇文章的磨練,現在可以一眼看出這就是dfs的代碼套路,只不過他傳的參數有點多,4個。
  這道題13分,這種填空題一定不能莽撞,他給出了程序代碼,自己填上答案之后,可以結合題意驗證一下,比如這道題他有說明總共會輸出101行結果,這就是一個檢驗條件。
  我第一次做的時候,完全是蒙的答案,如下:

f(a, k++, n, s2); //錯誤示例 

正確答案

f(a, k + 1, n - i, s2); 

  很顯然,我當時沒有搞懂dfs的搜索代碼,即下列代碼

for (int i = 0; i <= a[k]; i++) { _________________________// 填空位置 s2 += (char) (k + 'A'); } 

  既然他在main方法中調用了dfs算法,參數n傳入的是5,那么就代表觀察團的總人數要求是5人,這里的for循環進行搜索,一但選中 i 個人,那么接下來只能選 n - i 個人,所以參數應該是n - i,而不是n
  還有一點就是對於深搜這種,下一個情況是k+1,而不能用k++,或++k。原因是數組會越界,至於為什么會越界,我自己分析了一下,沒搞懂。最后就硬記住了,這就是套路,請按套路出牌。
  說實話,這道題如果不是填空題,而是一道大題,盡管我自認為理解了dfs算法,但還是寫不對代碼。還是要多理解理解這道題。

【第三道題】

  這篇文章的最后一道題
魔方狀態
  先說明,這道題到底怎么解,其實我也不知道,在這里寫它的原因是看到了下面這篇文章,不過作者說的答案:216,作者明知11112233和33221111是同一種知道去重,卻沒說出來12233111 和 11133221這樣之類的也是同一種,因此對於他的答案我不敢苟同。
【CSDN】sangjinchao《第八屆藍橋杯JAVAB組第四題》
  不過,就11112233全排列,這一單純的知識點我是很感興趣的。
  下面我想討論一下使用dfs算法就給定數字全排列問題,比如上面的數字四個1兩個2兩個3進行全排列,我使用了標記法,寫的代碼如下

 1 public class 全排列dfs {
 2 
 3     public static int[] a = new int[] { 1, 1, 1, 1, 2, 2, 3, 3 };
 4     public static int[] visited = new int[8];
 5     public static int[] result = new int[8];
 6     public static void dfs(int index) {
 7         // 結束條件
 8         if (index == 8) {
 9             for (int i : result) {
10                 System.out.print(i);
11             }
12             System.out.println();
13             return;
14         }
15         // 搜索
16         for(int i=0; i<8; i++) {
17             if(visited[i]==0) {
18                 visited[i] = 1;
19                 result[index] = a[i];
20                 dfs(index+1);
21                 visited[i] = 0;
22             }
23         }
24     }
25 
26     public static void main(String[] args) {
27         dfs(0);
28     }
29 
30 }

 

  不過,有些情況11112233和33221111,還有11221133和33112211這類的都算重復的,所以需要去掉。目前我給出一個不太成熟的代碼,只能想到這里了,如果有誰有優化的代碼,一定要給我打call告訴我

 1 public class 全排列dfs逆置去重 {
 2 
 3     public static int[] a = new int[] { 1, 1, 1, 1, 2, 2, 3, 3 };
 4     public static int[] visited = new int[8];
 5     public static int[] result = new int[8];
 6     public static int[] res = new int[33221112];
 7     public static int count = 0;
 8     public static void dfs(int index) {
 9         // 結束條件
10         if (index == 8) {
11             String s = "";
12             String rev = "";
13             StringBuilder sb= new StringBuilder();
14             for (int i : result) {
15                 sb.append(i);
16             }
17             s = sb.toString();
18             rev = sb.reverse().toString(); // 逆置
19             if(res[Integer.parseInt(rev)] == 0) {// 去重
20                 res[Integer.parseInt(s)] = 1;
21                 System.out.println(s);
22                 count++;
23             }
24             return;
25         }
26         // 搜索
27         for(int i=0; i<8; i++) {
28             if(visited[i]==0) {
29                 visited[i] = 1;
30                 result[index] = a[i];
31                 dfs(index+1);
32                 visited[i] = 0;
33             }
34         }
35     }
36 
37     public static void main(String[] args) {
38         dfs(0);
39         System.out.println(count);
40     }
41 
42 }

 

  這篇文章到這里就該結束了,我的初衷就是想告訴大家,dfs不僅僅是我在上篇文章里面寫的那篇,只能計算“湊算式”,dfs身為一種暴力破解方法,有很多種變形,還需要大家多加練習。
  有些人會擔心,都這個時候了復習藍橋杯,遲嗎?送你一句話:Latter Better Than Never!
  
  上一篇文章 → 《【算法】藍橋杯dfs深度優先搜索之湊算式總結》

  下一篇文章預計會在周五更新 → 《【算法】藍橋杯dfs深度優先搜索之圖連通總結》
  


參考文章:

    【CSDN】h1021456873《藍橋杯 牌型種數 (暴力||dfs)》

    【CSDN】豌豆苞谷《2017 第八屆藍橋杯 魔方狀態》

    【CSDN】sangjinchao《第八屆藍橋杯JAVAB組第四題》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM