LeetCode: combinations
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
解决这个问题有两种思路:
1. 从n个找出k个的所有组合,可以是从n-1个中找到k个的所有组合加上n-1个中找到k-1个的所有组合再加上n
f(n,k) = {f(n-1, k), [f(n-1, k-1), k] }
2. 从n个中找出k个的所有组合,可以是从n个中找到以n结尾的k个的组合,加上从n-1个中找到以n-1结尾的k个的组合,加上。。。。
f(n,k) = { f(n-1, k), f(n-2, k), ...., f(k, k) }
第一种方法可以用递归与dp两种方法。
1 public static ArrayList<ArrayList<Integer>> combine(int n, int k) { 2 ArrayList<ArrayList<Integer>>[][] arrayList = new ArrayList[n][k]; 3 4 arrayList[0][0] = new ArrayList<ArrayList<Integer>>(); 5 ArrayList<Integer> o = new ArrayList<Integer>(); 6 o.add(1); 7 arrayList[0][0].add(o); 8 9 for (int i=1; i<n; i++) { 10 for (int j=0; j<=i && j<k; j++) { 11 if (i!=j){ 12 arrayList[i][j] = new ArrayList<ArrayList<Integer>>(arrayList[i-1][j]); 13 } 14 else { 15 arrayList[i][j] = new ArrayList<ArrayList<Integer>>(); 16 } 17 18 if(j>0) { 19 ArrayList<ArrayList<Integer>> tmp = new ArrayList<ArrayList<Integer>>(); 20 for (ArrayList<Integer> s : arrayList[i-1][j-1]) { 21 ArrayList<Integer> t = new ArrayList<Integer>(s); 22 t.add(i+1); 23 tmp.add(t); 24 } 25 arrayList[i][j].addAll(tmp); 26 } 27 else { 28 ArrayList<Integer> w = new ArrayList<Integer>(); 29 w.add(i+1); 30 arrayList[i][j].add(w); 31 } 32 } 33 } 34 35 return arrayList[n-1][k-1]; 36 }
其中非常需要注意的是第20行,需要new一个t出来并复制s。如果直接在s上操作会影响之前的结果。
1 public static ArrayList<ArrayList<Integer>> combine2(int n, int k) { 2 ArrayList<ArrayList<Integer>> arrayList = new ArrayList<ArrayList<Integer>>(); 3 if ( k==1 ) { 4 for (int i=1; i<=n; i++) { 5 ArrayList<Integer> o = new ArrayList<Integer>(); 6 o.add(i); 7 arrayList.add(o); 8 } 9 10 } 11 else { 12 if ( n > k) { 13 ArrayList<ArrayList<Integer>> tmp = combine2(n-1, k); 14 arrayList.addAll(tmp); 15 } 16 for(ArrayList<Integer> s : combine2(n-1, k-1)) { 17 s.add(n); 18 arrayList.add(s); 19 } 20 } 21 22 return arrayList; 23 }
其中需要注意的是递归的终止条件。当k等于1的时候,是递归的终止条件。另外,在第12行需要判断n和k的关系,否则n<k是会一直递归下去,n可能为负数。
1 public static ArrayList<ArrayList<Integer>> combine3(int n, int k) { 2 if(n<k) return null; 3 ArrayList<ArrayList<Integer>> all = new ArrayList<ArrayList<Integer>>(); 4 if(k==1){ 5 for(int i=1;i<=n;i++){ 6 ArrayList<Integer> al = new ArrayList<Integer>(); 7 al.add(i); 8 all.add(al); 9 } 10 return all; 11 } 12 for(int i=n;i>=k;i--){ 13 for(ArrayList<Integer> al : combine3(i-1,k-1)){ 14 al.add(i); 15 all.add(al); 16 } 17 } 18 return all; 19 }
我又学会一种方法。。。DFS。比如4个里取3个,第一个数可以取1,2,3,4. 如果第一个数取1,那么第二个数可以取2,然后第三个取3。满足条件,返回这个结果,然后回溯到2那一步,不取3,改取4.[1,2,4]满足条件返回结果。。。。。。
需要注意两个地方。一个是第9行,一定要add new。因为如果直接add的话,因为ArrayList是对象,所以以后对tmp操作会直接影响result中的结果。
第二个是第16行,要删除掉最后添加的元素。当运行到第16行时,tmp为add当前循环里的i的那个状态,因为是从上一个combine回溯回来的。这个时候删除掉最后添加的这个元素,改为增加下一个元素的情况。。比如说tmp = [1, 2],删掉2,改为tmp=[1, 3]...
1 public static ArrayList<ArrayList<Integer>> combine4(int n, int k) { 2 ArrayList<Integer> tmp = new ArrayList<Integer>(); 3 ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>(); 4 combine4(1, n, k, tmp, result); 5 return result; 6 } 7 public static void combine4(int s, int n, int k, ArrayList<Integer> tmp, ArrayList<ArrayList<Integer>> result) { 8 if (tmp.size() == k) { 9 result.add(new ArrayList<Integer>(tmp)); 10 return; 11 } 12 else { 13 for (int i=s; i<=n; i++) { 14 tmp.add(i); 15 combine4(i+1, n, k, tmp, result); 16 tmp.remove(tmp.size()-1); 17 } 18 } 19 }