简介
- 时间复杂度:O(n!),这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n n-1 n-2 * ….. 1 = n!。
-
46.全排列
题目描述
解题思路
注意全排列和组合问题不一样的地方就是组合可以选取前面的数,也就是不受startIndex的限制,所以for从0开始循环。但是又遇到了一个问题,就是不能选取本身,所以需要树层去重。
递归函数参数
首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
- 递归终止条件
叶子节点就是结果,当path的长度和数组长度相等时,那么此时选择到了一个结果。
- 单层搜索的逻辑
这里和77.组合问题、131.切割问题和78.子集问题最大的不同就是for循环里不用startIndex了。
因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。
而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。
public List<List<Integer>> permute(int[] nums) {
if (nums.length == 0) {
return res;
}
backtracking(nums, new boolean[nums.length]);
return res;
}
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public void backtracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) { // 注意以前选择过的可以被选择,所以从0开始遍历
if (used[i]) continue;
path.add(nums[i]);
used[i] = true;
backtracking(nums, used);
used[i] = false;
path.removeLast();
}
}
47.全排列 II
题目描述
解题思路
这道题目和46.全排列的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。
此题题目给的数组是可以重复的,但排序结果又不重复,所以需要树枝和树层同时去重。
去重方法前面讲过,套用即可。
使用used来进行树层去重,注意需要排序
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums.length == 0) {
return res;
}
Arrays.sort(nums);
backtracking(nums, new boolean[nums.length]);
return res;
}
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public void backtracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) { // 排列从0开始
// used[i] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
// 如果同一树层nums[i - 1]使用过则直接跳过
// 注意需要排列
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { // 后一个在同一层判断上一个是否使用过
continue;
}
// 此时时used[i]判断这个元素是否使用过(树枝去重)
if (used[i] == true) {
continue; // 前一个判断path中是否使用过
}
path.add(nums[i]);
used[i] = true;
backtracking(nums, used);
used[i] = false;
path.removeLast();
}
}
使用哈希表进行树层去重
public List<List<Integer>> permuteUnique(int[] nums) {
if (nums.length == 0) {
return res;
}
backtracking(nums, new boolean[nums.length]);
return res;
}
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
/**
* 哈希法
*
* @param nums
* @param used
*/
public void backtracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
// 树层去重
if (set.contains(nums[i])) {
continue;
}
// 枝干去重
if (used[i] == true) {
continue;
}
set.add(nums[i]);
used[i] = true;
path.add(nums[i]);
backtracking(nums, used);
used[i] = false; // 回溯
path.removeLast();
}
}
剑指 Offer 38. 字符串的排列
题目描述
解题思路
树枝和树层去重,注意排序。
class Solution {
public String[] permutation(String s) {
char[] array = s.toCharArray();
Arrays.sort(array);
backTracking(array, new boolean[s.length()]);
return list.toArray(new String[list.size()]);
}
List<String> list = new ArrayList<>();
StringBuilder sb = new StringBuilder();
public void backTracking(char[] array, boolean[] used) {
if (sb.length() >= array.length) {
list.add(sb.toString());
return;
}
for (int i = 0; i < array.length; i++) {
if (i > 0 && array[i] == array[i - 1] && used[i - 1] == false) continue;
if (used[i] == true) continue;
sb.append(array[i]);
used[i] = true;
backTracking(array, used);
used[i] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
}
剑指 Offer 17. 打印从 1 到最大的 n 位数
具体见链接🔗