問題描述
題目原型大概如下:
學校要評優秀學生,有十個平時都很優秀的學生,他們之間不相上下,但是評選的名額有限,假設學生人數是n(0<n<=10),評選的名額數時m(0<m<=n),那么希望隨機從n名學生中選出m個學生,并且按字典序的列出所有可能的獲得優秀的學生名額。n個學生的序號時從 1....n.
示例
輸入:
5 2
輸出:
54
53
52
51
43
42
41
32
31
21
思路
假設n個學生的序號是[1,2,...,n],從中選m個學生,我們生成一個長度為m的數組,記錄選學生的序號索引,例如n=5,m=2,那么index=[0,1]是一開始的選擇學生的索引,index=[n-m,n-m+1]=[3,4]是最后一種情況,這里說明一下如何通過index數組求出編號,因為開始我們是從最小索引開始,那么用n-index[i] (0<=i<m)就是對應的學生編號,而且恰好能按照字典序由大到小的計算得到學生序號。
關鍵是在于index數組從開始態到啊結束態的不斷變化,其實細心點可以發現,我們每移動一次(相當于+1)index末尾元素就可以得到一種選擇的情況,而當最后一個元素加一后,其值超過它自己對應的最右位置時,我們就需要從最右開始找到與最右元素連續的索引位置,讓這部分元素的索引從左邊未移動過的最右那個元素的下一個位置開始標記自己的索引,而那個左邊未移動過的最右那個元素的索引也開始移動一位,以此往復,知道所有元素的索引位置都到達了自己能到達的最終位置,結束。
示例:
假設 n = 6,m=3;end=[3,4,5]對應m=3個學生移動的最終索引位置,index=[0,1,2];這是最開始的m=3個學生的索引位置。
start:
index : 0,1,2
stuno: 1,2,3,4,5,6
index末尾元素到達自己所能到達的最后位置(+1后超出)
index : 0,1, 5
stuno: 1,2,3,4,5,6
下一步的索引變化
index : 0, 2,3
stuno: 1,2,3,4,5,6
再比如
index : 0, 4,5
stuno: 1,2,3,4,5,6
下一步將是
index : 1,2,3
stuno: 1,2,3,4,5,6
最后將是
index : 3,4,5
stuno: 1,2,3,4,5,6
代碼
//注意end index中保存的索引時相對于n來說的范圍時[0,1,...,n-2,n-1]
public void getAllCase(int n,int m){
if(n<=0 || m<=0 || m>n)
return;
int end[] = new int[m]; //選中的m學生對應的末尾索引
int index[] = new int[m];//當前選中的m個學生的索引值
//初始化end 和 index
for (int i = 0; i < m; i++) {
end[i] = n - m + i;//計算第i個選中的學生能到達的末尾索引
index[i] = i;
}
int l = m - 1;//第m個學生(最后一個學生)對應于m的索引
while (true) {
print(index, n, m);
index[l]++; //每次都是第m個學生的索引值加一(最后一個)
if (index[l] > end[l]) { //超過他自己本來只能到達的最大索引,說明需要進行下一輪
int t = l;
//找到l位置之前已經達到末尾位置的第一個沒有到達對應末尾位置的的下標
while (t >= 0 && index[t] >= end[t]) {
t--;
}
//如果t大于等0說明上面while循環要找到的位置時存在的
if (t >= 0) {
int s = index[t];//上一步找到的第一個沒有到達對應末尾位置的元素(從右往左)對應于n的索引值。
//從t到m-1除的元素索引加一(相當于后移一位,依次排列在t之后,注意:第一次的t位置就已經開始加一)
while (t < m) {
index[t++] = ++s;
}
} else {//否則說明說明位置都已經到達末尾,既是所有情況都已經遍歷了一次
break;
}
}
}
}
//根據當前的索引數組答應選中的學生序號,因為索引數組時從0開始的,那么n-index[i]恰好得到對應的序號
private void print(int index[], int n, int m) {
for (int i = 0; i < m; i++) {
System.out.print(n - index[i]);
}
System.out.println();
}