聲明:把遞歸參數不變的,儘量統一設置爲成員變量。
一、全排列
全排列問題求解體系基本上分爲兩大類,一是基於選擇,二是基於交換。
1.1 全排列1:無重複
1.1.1 基於選擇的經典解法
優點是好記,遞歸時每次循環都從 0 開始。
class Solution {
private int[] nums;
private List<List<Integer>> result;
public List<List<Integer>> permute(int[] nums) {
this.nums = nums;
result = new ArrayList<>();
boolean[] used = new boolean[nums.length];
backtrack(new ArrayList<Integer>(), used);
return result;
}
private void backtrack(List<Integer> temp, boolean[] used){
if(temp.size() == nums.length){
result.add(new ArrayList<>(temp));
return;
}
for(int i = 0; i < nums.length; ++i){
if(used[i]) continue;
used[i] = true;
temp.add(nums[i]);
backtrack(temp, used);
temp.remove(nums[i]);
used[i] = false;
}
}
}
1.1.2 基於交換的經典解法
class Solution {
private List<List<Integer>> result;
public List<List<Integer>> permute(int[] nums) {
result = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
for(int e : nums) temp.add(e);
backtrack(temp, 0);
return result;
}
private void backtrack(List<Integer> temp, int start){
if(start == temp.size()) result.add(new ArrayList<>(temp));
for(int i = start; i < temp.size(); ++i){
Collections.swap(temp, i, start);
backtrack(temp, start+1);
Collections.swap(temp, i, start);
}
}
}
1.1.3 另一種基於交換的解法
class Solution {
private List<List<Integer>> result;
public List<List<Integer>> permute(int[] nums) {
result = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
for(int e : nums) temp.add(e);
backtrack(temp, 0);
return result;
}
private void backtrack(List<Integer> temp, int start){
result.add(new ArrayList<>(temp));
for(int i = start; i < temp.size(); ++i) {
for(int j = i+1; j < temp.size(); ++j) {
Collections.swap(temp, i, j);
backtrack(temp, i+1);
Collections.swap(temp, i, j);
}
}
}
}
1.2 全排列2:有重複
1.2.1 基於選擇的解法
這種解法,現在又不好記了,因爲去重的判斷比較複雜。
class Solution {
private int[] nums;
private List<List<Integer>> result;
public List<List<Integer>> permuteUnique(int[] nums) {
this.nums = nums;
result = new ArrayList<>();
boolean[] used = new boolean[nums.length];
Arrays.sort(nums);
backtrack(new ArrayList<>(), used);
return result;
}
private void backtrack(List<Integer> temp, boolean[] used){
if(temp.size() == nums.length){
result.add(new ArrayList<>(temp));
return;
}
for(int i = 0; i < nums.length; ++i){
if(used[i]) continue;
if(i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;
used[i] = true;
temp.add(nums[i]);
backtrack(temp, used);
used[i] = false;
temp.remove(temp.size()-1);
}
}
}
1.2.2 基於交換的解法(不能通過排序去重,因爲交換會打亂有序性)
有重複全排列的推薦解法!!!
class Solution {
private List<List<Integer>> result;
public List<List<Integer>> permuteUnique(int[] nums) {
result = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
for(int num : nums) temp.add(num);
backTrack(temp, 0);
return result;
}
public void backTrack(List<Integer> temp, int start) {
if(start == temp.size()) result.add(new ArrayList<>(temp));
Set<Integer> set = new HashSet<>(); // 數據較少的情況下,for 循環判重更好
for(int i = start; i < temp.size(); ++i){
if(set.contains(temp.get(i))) continue;
Collections.swap(temp, i, start);
backTrack(temp, start+1);
Collections.swap(temp, i, start);
set.add(temp.get(i));
}
}
}
枚舉當前層級的候選集時,第一個 2 已經和 1 交換,如果還讓第二個 2 和 1 交換,那麼下步交換就會導致重複。
用數學語言嚴謹地描述一下:
約定:[start, …] 表示 start 及其以後的所有元素。
枚舉當前層級的候選集時,分別用 [start, …] 和 start 交換,如果 [start, …] 中有兩個相同的元素分別和 start 交換,會得到兩個候選集,這兩個候選集的 [0, start] 部分是完全相等的,[start+1, …] 部分除了順序外也是相等的。這兩個候選集在後續層級的遞歸中,會分別得到 [start+1, …] 的全排列,因爲 start 只會變大,[0, start] 部分不會改變。所以這兩個候選集就會得到兩個相同的結果子集,即重複。
1.2.3 基於交換的另一種解法:
class Solution {
private List<List<Integer>> result;
public List<List<Integer>> permuteUnique(int[] nums) {
result = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
for(int e : nums) temp.add(e);
backtrack(temp, 0);
return result;
}
private void backtrack(List<Integer> temp, int li) {
result.add(new ArrayList<>(temp));
for(int i = li; i < temp.size(); ++i) {
Set<Integer> set = new HashSet<>();
set.add(temp.get(i));
for(int j = i+1; j < temp.size(); ++j) {
if(set.contains(temp.get(j))) continue;
Collections.swap(temp, i, j);
backtrack(temp, i+1);
Collections.swap(temp, i, j);
set.add(temp.get(j));
}
}
}
}
二、組合
無重複元素,基於選擇:
class Solution {
private List<List<Integer>> result;
public List<List<Integer>> combine(int n, int k) {
result = new ArrayList<>();
backtrack(n, k, 1, new ArrayList<>(k));
return result;
}
private void backtrack(int n, int k, int start, List<Integer> temp){
if(k == 0){
result.add(new ArrayList<>(temp));
return;
}
for(int i = start; i <= n-k+1; ++i){
temp.add(i);
backtrack(n, k-1, i+1, temp);
temp.remove(temp.size()-1);
}
}
}
三、組合總和
和爲給定值的所有組合。
3.1 組合總和1:無重複元素,元素可以多次被選中
class Solution {
private List<List<Integer>> result;
private int[] candidates;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
result = new ArrayList<>();
this.candidates = candidates;
backtrack(new ArrayList<Integer>(), 0, target);
return result;
}
private void backtrack(List<Integer> temp, int start, int target){
if(target == 0){
result.add(new ArrayList<>(temp));
return;
}
for(int i = start; i < candidates.length; ++i){
if(candidates[i] <= target){
temp.add(candidates[i]);
backtrack(temp, i, target-candidates[i]);
temp.remove(temp.size()-1);
}
}
}
}
3.2 組合總和2:有重複元素,每個元素只能使用一次
class Solution {
private List<List<Integer>> result;
private int[] candidates;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
result = new ArrayList<>();
this.candidates = candidates;
Arrays.sort(candidates);
backtrack(target, new ArrayList<>(), 0);
return result;
}
private void backtrack(int target, List<Integer> temp, int start){
if(target == 0){
result.add(new ArrayList<>(temp));
return;
}
for(int i = start; i < candidates.length; ++i){
if(i > start && candidates[i] == candidates[i-1]) continue;
if(candidates[i] <= target){
temp.add(candidates[i]);
backtrack(target-candidates[i], temp, i+1);
temp.remove(temp.size()-1);
}else return;
}
}
}
四、子集
4.1 子集1:無重複元素
二進制位掩碼
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
int n = 1 << nums.length;
for(int i = 0; i < n; ++i) {
List<Integer> subSet = new ArrayList<>();
for(int j = 0; j < nums.length; ++j) {
if((i & (1 << j)) != 0) {
subSet.add(nums[j]);
}
}
result.add(subSet);
}
return result;
}
}
4.2 子集2:有重複元素
class Solution {
private List<List<Integer>> result;
private int[] nums;
public List<List<Integer>> subsetsWithDup(int[] nums) {
result = new ArrayList<>();
this.nums = nums;
Arrays.sort(nums);
backtrack(0, new ArrayList<>());
return result;
}
private void backtrack(int k, List<Integer> temp){
result.add(new ArrayList<>(temp));
for(int i = k; i < nums.length; ++i){
if(i > k && nums[i] == nums[i-1]) continue;
temp.add(nums[i]);
backtrack(i+1, temp);
temp.remove(temp.size()-1);
}
}
}
除了全排列有基於交換的方法,其餘的都使用基於選擇的方法。