遞迴演算法(求n的加法組合,將一個整數拆分成多個整數相加的形式, O(N)時間,O(N)空間)
網上的多種解法比較複雜,本文用遞迴方法,22行程式碼搞定。時間和空間複雜度已經降到最低!
第三版:加入創作思路。
這個函式的主要功能就是輸出所有組合。既然是輸出所有的組合,那就意味著內部有一個遍歷所有組合的過程。既然是遍歷,而且是O(N)時間,那就說明這個遍歷是按照某種輸出次序,從“第一個組合”遍歷到“最後一個組合”。
如何給組合定義次序呢?舉例說明
例1:
3=1+1+1
3=1+2
3=3
上面的例子就說明了次序,即,按照組合中出現數字的從小到大順序。
定義了次序,剩下的就是如何讓程式按照這個程式一個一個的遍歷。遍歷的過程不會那麼完美的一個不重複,當然也會重複,這就涉及到過濾。過濾那些重複的元素,舉例說明
例2:
3=1+1+1
3=1+2
3=2+1
3=3
可以看出這個例2中的3=2+1被過濾掉了,並沒有輸出,這也是必須的,為什麼呢?因為3=2+1和之前出現的3=1+2本質上就是一種組合,還要交換一下數字的位置就可以了。而加法自然有交換率。所以就不必輸出了。從這裡還可以看出來過濾的依據,那就是讓一個組合中的所有數字也保持從小到大出現,這樣就不會出現3=2+1了,因為2比1大,之前肯定出現過了。這樣一來就解決了輸出的唯一性
至此,就剩下如遞迴的進行了。遞迴的思路是這樣的,例如拆分3:
既然
3=1+後續組合
那麼遞迴也就自然的變成了對“後續組合”進行繼續拆分,只要“後續組合“的所有排列找到了,後續組合的每個排列前面加上1這個字首自然就解決了1作為字首的所有情況,這樣一來就會遍歷到
3=1+1+1
3=1+2
由於組合次序的定義可以知道,1作為字首的情況被遍歷完之後,自然就變成了遍歷2開頭的數字
2開頭的數字還需要遍歷嗎?基於如下事實
n=m1+m2+...+mk, m1<m2<...<mk
第一個數字m1不會超過n/2,因為m1後面的數字要比m1大。所以可以看出,遍歷的時候第一個數字最多嘗試到n/2.
但是m1最大取n/2是合理的,比如
11=5+6
也就是說拆分的時候總是有拆分成兩個數的和的形式,其中n=m1+m2,m1<m2,這樣一來m1取n/2就是合適的。
那麼下面就是遞迴程式的實現了。
f(int n,list l,int start)
引數說明:
n:這裡n表示要對n進行拆分
l:這裡表示子拆分的時候字首的那些子遞增序列,當n被初次拆分的時候,list當然是空的,只有子拆分才會有非空的字首
start:表示在遍歷的時候當前組合的第一個數字,這個數字用來去除重複,參考輸出的唯一性
java實現:
public class Sum1ToN {
private void print(List<Integer> list) {
for (Integer k : list) {
System.out.print(k + "+");
}
}
private void f(int n, List<Integer> list, int start) {
if (n == 1) {
print(list);
System.out.println(1);
} else {
for (int i = start; i <=n / 2; i++) {
list.add(i);
f(n - i, list, i);
list.remove(list.size() -1);
}
print(list);
System.out.println(n);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
new Sum1ToN().f(9,list, 1);
}
}
輸出:f(9,null,1)