1 题目描述
2 算法思路
思路:深度优先遍历,先固定第一个字符,再固定第二个字符,最后固定第n个字符,然后回溯得到所有情况。
剪枝:当字符串中存在重复的字符时,就可以会出现重复的方案。因此在固定字符时,可以确保每个字符只在此处固定一次,即遇到重复的字符不交换,直接跳过。
递归流程:
- 当x = len(c) - 1 ,就代表固定完所有位了,将当前组合添加到res
- 递归参数:当前的固定位x
- 递归工作:初始化一个Set ,用来存储已经固定的字符,可以排除重复的字符,将第x位字符和 后面的字符分别进行交换,进入下层递归
- 剪枝:当c[i] 存在于Set中,就代表是重复字符,应该直接跳过
- 将c[i] 加入Set,便于后续剪枝
- 固定字符:将字符c[i] 和 c[x] 交换,也就是固定c[i]为当前字符
- 开启下层递归:dfs(x + 1),开始固定第x +1 个字符
- 回溯:将c[i] 和 c[x] 交换 ,也就是还原之前的交换,向上回溯
再总结:本题的解法,是通过固定x位置的字符,然后利用一个set来剪枝。
在进行排列组合时,使用的是交换, 即让第一个字符分别和每一个字符交换,然后递归进行,让下一个字符也进行交换,如此一来就会得到所有的组合。
3 代码
List<String> list = new ArrayList<>();
//为了让递归函数添加结果方便,定义到函数之外,这样无需带到递归函数的参数列表中
char[] c;
//同;但是其赋值依赖c,定义声明分开
public String[] permutation(String s) {
c = s.toCharArray();
//从第一层开始递归
dfs(0);
return list.toArray(new String[list.size()]);
//将字符串数组ArrayList转化为String类型数组
}
private void dfs(int x) {
//当递归函数到达第三层,就返回,因为此时第二第三个位置已经发生了交换
if (x == c.length - 1) {
list.add(String.valueOf(c));//将字符数组转换为字符串
return;
}
//为了防止同一层递归出现重复元素
HashSet<Character> set = new HashSet<>();
//这里就很巧妙了,第一层可以是a,b,c那么就有三种情况,这里i = x,正巧dfs(0),正好i = 0开始
// 当第二层只有两种情况,dfs(1)i = 1开始
for (int i = x; i < c.length; i++){
//发生剪枝,当包含这个元素的时候,直接跳过
if (set.contains(c[i])){
continue;
}
set.add(c[i]);
//交换元素,这里很是巧妙,当在第二层dfs(1),x = 1,那么i = 1或者 2, 要不是交换1和1,要不交换1和2
swap(i,x);
//进入下一层递归
dfs(x + 1);
//返回时交换回来,这样保证到达第1层的时候,一直都是abc。这里捋顺一下,开始一直都是abc,那么第一位置总共就3个位置
//分别是a与a交换,这个就相当于 x = 0, i = 0;
// a与b交换 x = 0, i = 1;
// a与c交换 x = 0, i = 2;
//就相当于上图中开始的三条路径
//第一个元素固定后,每个引出两条路径,
// b与b交换 x = 1, i = 1;
// b与c交换 x = 1, i = 2;
//所以,结合上图,在每条路径上标注上i的值,就会非常容易好理解了
swap(i,x);
}
}
private void swap(int i, int x) {
char temp = c[i];
c[i] = c[x];
c[x] = temp;
}
4 提交结果