题目描述
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
- 示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
限制:1 <= s 的长度 <= 8
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
以一个例子“abcd”来展开说明,我们知道一共有24种排列的方式
- 本身abcd是一种
- 将后两个字母交换:abdc,然后我们再换回来,回到abcd
- 将abcd的b和c交换:acbd
- 再将后两个字母交换:acdb,然后我们再换回来,成为acbd。再换回来,回到abcd
- 将abcd的b和d交换:adcb
- 再将后两个字母交换:adbc,然后我们再换回来,成为adcb。再换回来,回到abcd
- 将abcd的a和b交换:bacd
- 再将后两个字母交换:badc,然后我们再换回来,成为bacd
- 将bacd的a和c交换:bcad
- 再将后两个字母交换:bcda,然后我们再换回来,成为bcad。再换回来,成为bacd
- 将bacd的a和d交换:bdca
- 再将后两个字母交换:bdac。再换回去,成为bdca。再换回来,成为bacd。再换回来,回到abcd
- 将abcd的a和c交换:cbad
- 再将后两个字母交换:cbda,然后我们再换回来,成为cbad
- 将cbad的b和a交换:cabd
- 再将后两个字母交换:cadb,然后我们再换回来,成为cabd。再换回来,成为cbad
- 将cbad的b和d交换:cdab
- 再将后两个字母交换:cdba,然后我们再换回来,成为cdab。再换回来,成为cbad,再换回来,回到abcd
- 将abcd的a和d交换:dbca
- 再将后两个字母交换:dbac
- 将dbca的b和c交换:dcba
- 再将后两个字母交换:dcab,然后我们再换回来,成为dcba。再换回来,成为cbca
- 将dbca的b和a交换:dacb
- 再将后两个字母交换:dabc,然后我们再换回来,成为dacb。再换回来,成为dbca。再换回来回到abcd。
我们发现存在一个回溯的操作,因此这里使用递归+回溯的算法
此外同一个字母可能出现多次,为防止重复,使用Set集合去重
代码详解
class Solution {
char[] cs; // 将String拆成char[]数组,方便字母交换
Set<String> set; // 防止字符串重复
public String[] permutation(String s) {
cs = s.toCharArray();
set = new HashSet<>();
dfs(0, s.length()); // 递归完毕后,set集合里便是全部的答案了
int index = 0;
String[] ss = new String[set.size()];
for(String _s : set) {
ss[index] = _s;
++index;
}
return ss;
}
public void dfs(int deep, int limit) {
for(int i = deep; i < limit; ++i) { // 递归的开始均是自己与自己交换,代表“自身”这一种情况
set.add(String.valueOf(cs));
swap(i, deep);
dfs(deep+1, limit); // 递归
swap(i, deep); // 回溯
}
}
// 交换字母的方法
public void swap(int i, int j) {
char c = cs[i];
cs[i] = cs[j];
cs[j] = c;
}
}
注意点
- 递归的开始是自己与自己进行交换,看似做无用功,但是对于递归的整个流程而言是不可或缺的
- 对于在字符串上频繁进行修改的操作,不建议直接在字符串上进行操作,比如交换、替换用char[],拼接用StringBuilder对象等