題目:
n個數1,2,...,n,從這n個數中任意選m個數,輸出所有不同組合,共有C(n,m)種不同組合。
如n=4,m=2,會產生如下輸出:
1 2
1 3
2 3
1 4
2 4
3 4
如n=5,m=3,會產生如下輸出:
1 2 3
1 2 4
1 3 4
2 3 4
1 2 5
1 3 5
2 3 5
1 4 5
2 4 5
3 4 5
題解:
1. 題解一:(正向打印)
1. 選擇第i(m <=i<=n)個元素作爲每個組合的最後元素,在第1————i個元素中往前(前i - 1個元素中)選取m-1個元素。
2. 若m等於1(對應b[0]),則表示選完,輸出該組合(數組b中存儲的是組合的元素在a中的下標)
3. 若m>1,則重複1、2步驟
例如:
從後往前選取,選定位置i後,再在前i-1個裏面選取m-1個。
如 1 2 3 4 5 中選取 3 個
1、如果不包含5、也不包含4,直接選取3,那麼再在前2個裏面選取2個,剛好只有兩個。
2、如果只不包含5,直接選定4,那麼再在前3個裏面選取2個,而前3個裏面選取2個又是一個子問題,遞歸即可。
3、選取5後,再在前4個裏面選取2個,而前4個裏面選取2個又是一個子問題,遞歸即可。
縱向看,1、2、3剛好是一個for循環,初值爲m(m == 3),終值爲n(n == 5)
橫向看,該問題爲一個前i-1箇中選m-1的遞歸。
2. 題解二:(反向打印)
組合問題就是從n中選m個數,也是採用遞歸的方式
a. 首先從n個數中選取編號最大的數,然後在剩下的n-1個數裏面選取m-1個數,直到從n-(m-1)個數中選取1個數爲止。
b. 從n個數中選取編號次小的一個數,繼續執行1步,直到當前可選編號最大的數爲m。
代碼:
1. 代碼一:(正向打印)
import java.util.*;
public class Main {
public static void C(int n, int m, int a[], int b[])
{
for(int i = m; i <= n; i++)
{
b[m - 1] = i - 1;
if(m > 1)
{
C(i - 1, m - 1, a, b);
}
else
{
for(int j = 0; j < b.length; j++)
{
System.out.printf("%d ", a[b[j]]);
}
System.out.println();
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 4
int m = sc.nextInt(); // 2
int a[] = new int[n];
for(int i = 0; i < n; i++)
{
a[i] = i + 1; // a[0, 1, 2, 3] = { 1, 2, 3, 4};
}
int b[] = new int[m]; // 數組b中存儲的是組合的元素在a中的下標
C(n, m, a, b);
sc.close();
}
}
// // 輸入:
// 4 2
// // 輸出:
// 12
// 13
// 23
// 14
// 24
// 34
2. 代碼二:(反向打印)
/// 求從數組a[1..n]中任選m個元素的所有組合。
/// a[1..n]表示候選集,n爲候選集大小,n>=m>0。
/// b[1..M]用來存儲當前組合中的元素(這裏存儲的是元素下標),
/// 常量M表示滿足條件的一個組合中元素的個數,M=m,這兩個參數僅用來輸出結果。
void combine( int a[], int n, int m, int b[], const int M )
{
for(int i=n; i>=m; i--) // 注意這裏的循環範圍
{
b[m-1] = i - 1;
if (m > 1)
combine(a,i-1,m-1,b,M);
else // m == 1, 輸出一個組合
{
for(int j=M-1; j>=0; j--)
cout << a[b[j]] << " ";
cout << endl;
}
}
}