这一类问题一般都是给出一个数组或集合,从中选出若干进行排列、组合、计算、比较…等操作,求有多少种选择方案。
如果类似,那么恭喜你,遇到经典的排列组合问题了。
一般来讲,排列组合问题主要分为三种不同的类型,分别为:
- 数组无重、不可复选
- 数组有重、不可复选
- 数组无重、可以复选
废话少说,直接步入正题。
【注:本文给出的集合全部以数组为例】
1、元素无重、不可复选
// Q1:求所有子集
class Solution {
List<List<Integer>> ans = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>(); // Point 1
// 回溯方法
void backtrack(int[] nums, int start) {
ans.add(new LinkedList<>(track));
for(int i = start; i < nums.length; i++) { // Point 2
track.add(nums[i]);
backtrack(nums, i + 1); // Point 3
track.removeLast();
}
}
// 执行方法
public List<List<Integer>> subsets(int[] nums) {
backtrack(nums, 0);
return ans;
}
}
// Q2:求组合。C(n, k)
class Solution {
List<List<Integer>> ans = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
// 回溯方法
void backtrack(int n, int k, int start) {
if(track.size() == k) { // 记录长度为 k 的 track
ans.add(new LinkedList<Integer>(track));
}
for(int i = start; i <= n; i++) {
track.add(i);
backtrack(n, k, i + 1);
track.removeLast();
}
}
// 执行方法
public List<List<Integer>> combine(int n, int k) {
backtrack(n, k, 1);
return ans;
}
}
// 求全排列。A(n, n)
class Solution {
List<List<Integer>> ans = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
// 回溯方法
void backtrack(int[] nums, boolean[] visited) {
if(track.size() == nums.length) {
ans.add(new LinkedList<>(track));
}
for(int i = 0; i < nums.length; i++) {
if(visited[i]) continue; // 注意这里 初始值 0 和 visited 数组组合来使用
track.add(nums[i]);
visited[i] = true;
backtrack(nums, visited);
visited[i] = false;
track.removeLast();
}
}
// 执行方法
public List<List<Integer>> permute(int[] nums) {
boolean[] visited = new boolean[nums.length];
backtrack(nums, visited);
return ans;
}
}
注意:
Point 1 处实例化为 LinkedList 是为了保证插入、移除有序。
Point 2 && Point 3 组合避免出现重复子集。
2、数组有重、不可复选
// 可能出现重复元素的 数组,求所有不重复 子集。
class Solution {
List<List<Integer>> res = new LinkedList<>();
Set<LinkedList<Integer>> ans = new LinkedHashSet<>(); // 用集合来去重
LinkedList<Integer> track = new LinkedList<>();
// 回溯方法
void backtrack(int[] nums, int start) {
ans.add(new LinkedList<Integer>(track));
for(int i = start; i < nums.length; i++) {
track.add(nums[i]);
backtrack(nums, i + 1);
track.removeLast();
}
}
// 集合转换方法
void setToList() {
for(LinkedList track : ans) {
res.add(new LinkedList<Integer>(track));
}
}
// 执行方法
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums); // 一定要注意先排序
backtrack(nums, 0);
setToList();
return res;
}
}
// 可能出现重复元素的 数组,求不重复 全排列。
class Solution {
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
Set<LinkedList<Integer>> ans = new LinkedHashSet<>(); // 去重集合
// 回溯方法
void backtrack(int[] nums, boolean[] visited) {
if(nums.length == track.size()) {
ans.add(new LinkedList<>(track));
}
for(int i = 0; i < nums.length; i++) {
if(visited[i]) continue;
track.add(nums[i]);
visited[i] = true;
backtrack(nums, visited);
track.removeLast();
visited[i] = false;
}
}
void setToList() {
for(LinkedList track : ans) {
res.add(new LinkedList<Integer>(track));
}
}
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] visited = new boolean[nums.length];
Arrays.sort(nums); // 先排序
backtrack(nums, visited);
setToList();
return res;
}
}
3、元素无重、可以复选
// 无重复元素的 数组,求和为 target 的所有 组合。
class Solution {
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
int trackSum = 0; // 记录 track 当前元素总和
// 回溯方法
void backtrack(int[] nums, int target, int start) {
if(trackSum == target) { // Base Case 1
res.add(new LinkedList<Integer>(track));
}
if(trackSum > target) return; // Base Case 2
for(int i = start; i < nums.length; i++) {
track.add(nums[i]);
trackSum += nums[i];
backtrack(nums, target, i); // 可重复选择的 关键 在这里,传入的是 i 而不是 i+1
track.removeLast();
trackSum -= nums[i];
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtrack(candidates, target, 0);
return res;
}
}