Subsets与Permutation这两种题型如果用Recursion的方法来解决,其思路是及其相似的。
1.Subsets I
内容
Given a set of distinct integers, S, return all possible subsets.
Note:
Elements in a subset must be in non-descending order.The solution set must not contain duplicate subsets.
For example,If S = [1,2,3], a solution is:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
思路
这道题是求一个集合的所有子集,对于集合中的每个元素有两种选择,放进集合中和不放进集合中。可以用树型结构表示该过程。
另外,由于递归无法有返回值,每个子集(ArrayList<Integer>
)在递归过程中可以作为参数加到最后的结果(ArrayList<ArrayList<Integer>>
)中。这种情况在使用Java语言的递归中尤其常见。
代码
public class Solution {
public ArrayList<ArrayList<Integer>> subsets(int[] num) {
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
if(num == null || num.length == 0) {
return result;
}
ArrayList<Integer> list = new ArrayList<Integer>();
Arrays.sort(num);
subsetsHelper(result, list, num, 0);
return result;
}
private void subsetsHelper(ArrayList<ArrayList<Integer>> result,
ArrayList<Integer> list, int[] num, int pos) {
result.add(new ArrayList<Integer>(list));
for (int i = pos; i < num.length; i++) {
list.add(num[i]);
subsetsHelper(result, list, num, i + 1);
list.remove(list.size() - 1);
}
}
}
重点
- 前面的边界检查,如果不存在则返回空集。
- subsetsHelper则为递归程序,子集被当成参数传递使得递归过程中数据不会丢失,还可以在类中增加私有成员进行储存。
- for循环中的add和remove方法是关键,代表放进和不放进两种选择,这两个方法的位置可以看成是树形结构的向下走和往回走。
2.Subsets II
内容
Given a collection of integers that might contain duplicates, nums, return all possible subsets.
Note:
Elements in a subset must be in non-descending order.The solution set must not contain duplicate subsets.
For example,If nums = [1,2,2], a solution is:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
思路
Subsets II 与 Subsets I 的区别仅在于集合中存在重复元素。因此,如果使用 Subsets I 的方法就会导致最终的结果集中存在一些重复的子集(注意:集合中的元素之间是没有次序关系的)。
此时在 Subsets I 的解题思路基础上,加一些特殊处理即可。以题干中的例子为例,将集合中的重复元素2特殊处理,在每一次的递归过程中要么取第一个2放进集合,要么所有的2都不选进集合中。
代码
此处仅贴出与第一部分不同,即进行特殊处理的代码。
private void subsetsHelper(ArrayList<ArrayList<Integer>> result,
...
for (int i = pos; i < num.length; i++) {
if ( i != pos && num[i] == num[i - 1]) {
continue;
}
...
}
}
重点
由于每次递归调用函数时,pos的值会变化,因此是可以把所有重复的2选进集合的,确保了存在2的子集的单一性和正确性。
3.Permutation I
内容
Given a collection of distinct numbers, return all possible permutations.
For example,[1,2,3] have the following permutations:[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1].
思路
此题解题思路与上题很像,区别在与此题没有位置关系。换种说法,此题可以解读为给出几个元素,生成这些元素的排列组合,因此元素之间是没有位置关系的。
代码
public class Solution {
public ArrayList<ArrayList<Integer>> permute(int[] num) {
ArrayList<ArrayList<Integer>> rst = new ArrayList<ArrayList<Integer>>();
if (num == null || num.length == 0) {
return rst;
}
ArrayList<Integer> list = new ArrayList<Integer>();
helper(rst, list, num);
return rst;
}
public void helper(ArrayList<ArrayList<Integer>> rst, ArrayList<Integer> list, int[] num){
if(list.size() == num.length) {
rst.add(new ArrayList<Integer>(list));
return;
}
for(int i = 0; i<num.length; i++){
if(list.contains(num[i])){
continue;
}
list.add(num[i]);
helper(rst, list, num);
list.remove(list.size() - 1);
}
}
}
重点
此代码在思路上与 Subsets 相似易于理解且在可以套用在Permutation II上。
4.Permutation II
内容
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
For example,[1,1,2] have the following unique permutations:[1,1,2], [1,2,1], and [2,1,1].
思路
主要特殊解决重复问题,与 Subsets II 极为相似。
代码
代码与 Permutation I 一样,主要是下面这段代码解决了重复的问题。
public void helper(ArrayList<ArrayList<Integer>> rst, ArrayList<Integer> list, int[] num){
...
for(int i = 0; i<num.length; i++){
if(list.contains(num[i])){
continue;
}
...
}
}
}
5.总结
任何递归程序均可写成非递归的形式,一般可以有两种方式改写:使用编码方式、使用栈(最常用)。如果一个程序使用非递归形式易懂,那么应该使用非递归形式,因为递归形式毕竟有调用程序的开销。这里给出编码方式解决 Subsets I 的代码。
class Solution { public ArrayList<ArrayList<Integer>> subsets(int[] nums) { ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>(); int n = nums.length; Arrays.sort(nums); // 1 << n is 2^n // each subset equals to an binary integer between 0 .. 2^n - 1 // 0 -> 000 -> [] // 1 -> 001 -> [1] // 2 -> 010 -> [2] // .. // 7 -> 111 -> [1,2,3] for (int i = 0; i < (1 << n); i++) { ArrayList<Integer> subset = new ArrayList<Integer>(); for (int j = 0; j < n; j++) { // check whether the jth digit in i's binary representation is 1 if ((i & (1 << j)) != 0) { subset.add(nums[j]); } } result.add(subset); } return result; } }
相似题型(后续补充)