简介
- 时间复杂度: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 位数
具体见链接🔗

