1. 程式人生 > >遞迴演算法(求n的加法組合,將一個整數拆分成多個整數相加的形式, O(N)時間,O(N)空間)

遞迴演算法(求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)