所謂排列,是指從給定的元素序列中依次取出元素,需要考慮取出順序。比如,取出元素3, 5,因取出順序的不同,則形成的序列{3, 5}與{5, 3}是不同的排列序列。對於長度為n的元素序列取出k個元素,則共有A(n, k)種取法。所謂組合,也是從元素序列中依次取出元素,與排列不同的是不需要考慮取出順序;因此其取法數為C(n, k)。
LeetCode有兩個問題分屬於組合、排列:77. Combinations 與 46. Permutations。
組合
要求給出對於序列1~n 的取出k個元素的各種取法。采用DFS模擬組合時,可看做節點i與節點j(j = i+1, … , n)都相連接,然后DFS遍歷整張有向圖,代碼實現如下:
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result = new ArrayList<>();
if (n <= 0 || n < k) {
return result;
}
List<Integer> tmp = new ArrayList<>();
dfs(n, k, 1, tmp, result);
return result;
}
// DFS for combination
private void dfs(int n, int k, int start,
List<Integer> tmp, List<List<Integer>> result) {
if (tmp.size() == k) {
result.add(new ArrayList<Integer>(tmp));
return;
}
for (int i = start; i <= n; i++) {
tmp.add(i);
dfs(n, k, i + 1, tmp, result);
tmp.remove(tmp.size() - 1); // remove the last
}
}
排列
DFS實現排列與組合相類似,唯一不同之處在於,節點i與其他所有節點都連接。因此,所構造的圖是一個完全連通圖。DFS實現排列如下:
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums.length == 0) {
return result;
}
List<Integer> tmp = new ArrayList<>();
dfs(nums, tmp, result);
return result;
}
// DFS for permutation
private void dfs(int[] nums, List<Integer> tmp,
List<List<Integer>> result) {
int n = nums.length;
if (tmp.size() == n) {
result.add(new ArrayList<>(tmp));
return;
}
for (int i = 0; i < n; i++) {
// nums[i] has not been visited
if (!tmp.contains(nums[i])) {
tmp.add(nums[i]);
dfs(nums, tmp, result);
tmp.remove(tmp.size() - 1);
}
}
}
上述代碼中,可以用一個visit數組來標記節點是否被訪問,這樣優化將contains的時間復雜度從\(O(n)\)優化到\(O(1)\)。