題目說明 :
考慮某個數字可能的組合或拆解, 這邊以數字 3,4,5 進行說明 :
3(第1種) = 2+1(第2種) = 1+1+1(第3種) 所以3有三種拆法
4(1) = 3 + 1(2) = 2 + 2(3) = 2 + 1 + 1(4) = 1 + 1 + 1 + 1(5) 共五種
5(1) = 4 + 1(2) = 3 + 2(3) = 3 + 1 + 1(4) = 2 + 2 + 1(5) = 2 + 1 + 1 + 1(6) = 1 + 1 +1 +1 +1(7) 共七種
依此類推,請問一個指定數字NUM的拆解方法個數有多少個?
解法 :
我們以上例中最後一個數字5的拆解為例,假設 f(n) 為數字 n 的可拆解方式個數,而 f(x,y) 為使用 y(含) 以下的數字來拆解 x 的方法個數,則觀察 :
5 = 4 + 1 = 3 + 2 = 3 + 1 + 1 = 2 + 2 + 1 = 2 + 1 + 1 + 1 = 1 + 1 +1 +1 +1
使用函式來表示的話 :
f(5) = f(5, 5) + f(5, 4) + f(5, 3) + f(5, 2) + f(5 ,1)
其中 f(5, 5) 與 f(5, 1) 都會是1, 因為一個數字使用小於等於一來組合該數字的可能性只有一種. 而 f(5, 3)=2, f(5, 2)=2...
接著我們觀察 f(5, 2), 可以看成 2 + (...). 而 (...) 的合為 5-2=3 (使用小於2的數字來組合). 故 f(5, 2) 可以看成 f(3, 2) + f(3, 1)!
如果是 f(5, 3), 可以看成 3 + (...). 而 (...) 的合為 5-3=2. 因此 f(5, 3) 可以看成 f(2, 3) = f(2, 2). 用更精確的寫法就是 :
f(x, y) = f(x-y, min((x-y), y)) ; f(1, y)=f(x, 1)=f(x, x) or f(y, y)=1
實作 :
- NumberSet.java :
- package alg.set;
- public class NumberSet {
- private int num=0;
- public NumberSet(){}
- public NumberSet(int num)
- {
- this.num = num;
- }
- public int subFun(int x, int y)
- {
- if(x==0 || y==0) return 0; // s(0,y) or s(x,0) = 0
- else if(x==1 || y==1) return 1; // s(1,y) or s(x,1) = 1
- else if(x==y) return 1; // s(x,y) under x=y, = 1
- else if(y>x) // s(x,y) under y>x, = f(x)
- {
- return numSetfun(x);
- }
- else
- {
- // s(x,y) under x>y and z=x-y then
- // if z<=y, then f(z)
- // if z>y , then s(z,y)+s(z,y-1)+...+s(z,1)
- int z = x-y;
- if(z<=y) return numSetfun(z);
- else
- {
- int sum = 0;
- for(int i=y; i>=1; i--) sum+=subFun(z,i);
- return sum;
- }
- }
- }
- public int numSetfun(){return numSetfun(num);}
- public int numSetfun(int x){
- int sum = 0;
- for(int i=x; i>=1; i--) sum+=subFun(x, i); // f(x)=s(x,x-1)+s(x,x-2)...+s(x,1)
- return sum;
- }
- public static int min(int x, int y) {return x>y?y:x;}
- /**
- * BD :
- * Using Dynamic programming
- * @param x
- * @return
- */
- public int dymNumsetFun(int x)
- {
- // | 1 2 3 4
- // ====================================
- // 1 | s(1,1) s(1,2) s(1,3) s(1,4)
- // 2 | s(2,1) s(2,2) s(2,3) s(2,4)
- // 3 | s(3,1) s(3,2) s(3,3) s(3,4)
- // 4 | s(4,1) s(4,2) s(4,3) s(4,4)
- //
- // Based on this matrix, f(3)=s(3,3)+s(3,2)+s(3,1)
- if(x==0) return 0; // f(0)=0
- else if(x==1) return 1; // f(1)=1
- else if(x==2) return 2;
- else
- {
- int mtx[][] = new int[x][x];
- for(int i=0; i
- {
- mtx[0][i]=1; // s(1,y)=1
- mtx[i][0]=1; // s(x,1)=1
- mtx[i][i]=1; // s(x,y) and x=y, =1
- }
- for(int i=2; i
- for(int j=1; j<=i; j++)
- {
- int z = i-j;
- int m = min(z,j+1)-1;
- for(int k=0; k<=m; k++) {
- mtx[i][j]+=mtx[z-1][k];
- }
- }
- int sum=0;
- for(int i=0; i
1][i]; - return sum;
- }
- }
- public int getNum() {
- return num;
- }
- public void setNum(int num) {
- this.num = num;
- }
- public static void main(String args[])
- {
- //NumberSet ns = new NumberSet(4);
- //System.out.println("Number "+ns.getNum()+" has "+ns.numSetfun()+" possible sets.");
- System.out.println("=====Using Dynamic Programming=====");
- NumberSet ns = new NumberSet();
- long st = System.currentTimeMillis();
- for(int i=50; i<=55; i++)
- {
- System.out.println("Number "+i+" has "+ns.dymNumsetFun(i)+" possible sets.");
- }
- System.out.println("\t[Performance] Spending time="+(System.currentTimeMillis()-st)+" ms");
- System.out.println("=====Using Recursive Method=====");
- st = System.currentTimeMillis();
- for(int i=50; i<=55; i++)
- {
- System.out.println("Number "+i+" has "+ns.numSetfun(i)+" possible sets.");
- }
- System.out.println("\t[Performance] Spending time="+(System.currentTimeMillis()-st)+" ms");
- }
- }
執行結果 :
=====Using Dynamic Programming=====
Number 50 has 204226 possible sets.
Number 51 has 239943 possible sets.
Number 52 has 281589 possible sets.
Number 53 has 329931 possible sets.
Number 54 has 386155 possible sets.
Number 55 has 451276 possible sets.
[Performance] Spending time=11
=====Using Recursive Method=====
Number 50 has 204226 possible sets.
Number 51 has 239943 possible sets.
Number 52 has 281589 possible sets.
Number 53 has 329931 possible sets.
Number 54 has 386155 possible sets.
Number 55 has 451276 possible sets.
[Performance] Spending time=38
可以看出來使用 Dynamic programming 比用 Recursive method 更有效率, 但是使用 Recursive method 寫的 code 會比較簡潔且易讀!
沒有留言:
張貼留言