题目:
给出n, k。求从1~n里面取k个的不同组合。(不能有重复的)
乍一看,这不就和全排列很像吗?不就是= k时记录然后return就好了。但是如果按排列做的话,会有很多重复的。如 [1, 4] 和 [4, 1] 在排列中算不一样的,但在组合中是一样的,和元素出现的顺序无关。所以如果按排列做,会很麻烦,就像我第一次下面这样做的。为了避免超时,各种“优化”。。。(实在是很不推荐)
1 class Solution { 2 private List<List<Integer>> ans = new ArrayList<>(); 3 private Set<String> markers = new HashSet<>(); 4 public List<List<Integer>> combine(int n, int k) { 5 ans.clear(); 6 markers.clear(); 7 if (n <= 0) 8 return ans; 9 if (k > n || k < 0) 10 return ans; 11 int[] nums = new int[n]; 12 StringBuilder sb = new StringBuilder(); 13 for (int i = 0; i < nums.length; i++) { 14 nums[i] = i + 1; 15 sb.append(0); 16 } 17 if (k >= n / 2) { 18 helper(nums, n, sb, n - k, 0, false); 19 } 20 else { 21 helper(nums, n, sb, k, 0, true); 22 } 23 for (String item : markers) { 24 List<Integer> elem = new ArrayList<>(); 25 for (int i = 0; i < item.length(); i++) { 26 if (item.charAt(i) == '1') { 27 elem.add(i + 1); 28 } 29 } 30 ans.add(elem); 31 } 32 return ans; 33 } 34 35 public void helper(int[] nums, int n, StringBuilder sb, int k, int size, boolean flag) { 36 if (size >= k) { 37 String ts = sb.toString(); 38 if (!flag) { 39 bitReverse(sb); 40 ts = sb.toString(); 41 bitReverse(sb); 42 } 43 if (!markers.contains(ts)) 44 markers.add(ts); 45 return; 46 } 47 for (int i = 0; i < n; i++) { 48 sb.setCharAt(nums[i] - 1, '1'); 49 swap(nums, i, n - 1); 50 helper(nums, n - 1, sb, k, size + 1, flag); 51 swap(nums, i, n - 1); 52 sb.setCharAt(nums[i] - 1, '0'); 53 } 54 return; 55 } 56 57 public void swap(int[] nums, int i, int j) { 58 int tmp = nums[i]; 59 nums[i] = nums[j]; 60 nums[j] = tmp; 61 return; 62 } 63 64 public void bitReverse(StringBuilder sb) { 65 for (int i = 0; i < sb.length(); i++) { 66 if (sb.charAt(i) == '0') 67 sb.setCharAt(i, '1'); 68 else 69 sb.setCharAt(i, '0'); 70 } 71 } 72 }
然后看了讨论区的解法,发现其实很简单。
1 class Solution { 2 private List<List<Integer>> ans = new ArrayList<>(); 3 4 public List<List<Integer>> combine(int n, int k) { 5 ans.clear(); 6 helper(new ArrayList<Integer>(), n, k, 1); 7 return ans; 8 } 9 10 public void helper(List<Integer> tmp, int n, int k, int start) { 11 if (k == 0) { 12 ans.add(new ArrayList<Integer>(tmp)); 13 return; 14 } 15 16 //选了某个元素i后,下一次就从i后面的开始选。 17 for (int i = start; i <= n - (k - 1); i++) { 18 tmp.add(i); 19 helper(tmp, n, k - 1, i + 1); 20 tmp.remove(tmp.size() - 1); 21 } 22 } 23 }
关键在于:既然组合是和元素次序无关的,我们不妨就按升序去构造。这样当我们选了某个元素i后,为了避免重复,下一次就从i后面的元素开始选,直到选完k个元素为止。这里还有一个小小的优化,就是可以把for循环中的i <= n改成i <= n - (k - 1)。因为当i > n - (k - 1)时,就算把n - (k - 2)到n的全部元素都选进去,也只有k - 1个,即无法达到k个元素。所以大于的情况可以忽略不计。这样优化后,不论时间复杂度还是空间复杂度,都得到了较大的改善。