有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。
示例 1:
输入:S = "qqe" 输出:["eqq","qeq","qqe"]
示例 2:
输入:S = "ab" 输出:["ab", "ba"]
class Solution {
public String[] permutation(String S) {
Set<String> set = new HashSet<>();
int len = S.length();
List<Character> list = new ArrayList<>();
for (int i = 0; i < len; i++) {
list.add(S.charAt(i));
}
backtrack(list, new StringBuilder(len), set);
return set.toArray(new String[0]);
}
private void backtrack(List<Character> list, StringBuilder sb, Set<String> set) {
if (list.isEmpty()) {
set.add(sb.toString());
return;
}
for (int i = 0; i < list.size(); i++) {
char ch = list.remove(i);
sb.append(ch);
backtrack(list, sb, set);
sb.delete(sb.length()-1, sb.length());
list.add(i, ch);
}
}
}
-
创建
HashSet
来存储结果,利用Set的特性自动去重 -
将输入字符串转换为
ArrayList<Character>
,方便后续操作 -
调用
backtrack
方法进行回溯生成所有排列 -
最后将Set转换为String数组返回
-
基线条件:当
list
为空时,说明已经生成了一个完整的排列,将其加入结果集 -
循环处理:遍历当前可用的所有字符
-
取出当前字符
list.remove(i)
-
将字符加入当前排列
sb.append(ch)
-
递归处理剩余字符
backtrack(list, sb, set)
-
回溯操作:
-
从排列中移除最后添加的字符
sb.delete()
-
将字符放回原位置
list.add(i, ch)
-
-
举例说明(输入 S = "aab"
)
初始状态
-
list = ['a', 'a', 'b']
(可选的字符) -
sb = ""
(当前已选的字符) -
set = []
(结果集)
递归过程(逐层展开)
-
第一层递归(i=0):
-
选择
list[0]='a'
:-
list
移除'a'
→['a', 'b']
-
sb
追加'a'
→"a"
-
进入递归:
-
第二层递归(i=0):
-
选择
list[0]='a'
:-
list
移除'a'
→['b']
-
sb
追加'a'
→"aa"
-
进入递归:
-
第三层递归(i=0):
-
选择
list[0]='b'
:-
list
移除'b'
→[]
-
sb
追加'b'
→"aab"
-
终止条件:
list
为空,set = ["aab"]
-
回溯:
-
sb
删除'b'
→"aa"
-
list
插回'b'
→['b']
-
-
-
-
回溯:
-
sb
删除'a'
→"a"
-
list
插回'a'
→['a', 'b']
-
-
-
-
-
第二层递归(i=1):
-
选择
list[1]='b'
:-
list
移除'b'
→['a']
-
sb
追加'b'
→"ab"
-
进入递归:
-
第三层递归(i=0):
-
选择
list[0]='a'
:-
list
移除'a'
→[]
-
sb
追加'a'
→"aba"
-
终止条件:
set = ["aab", "aba"]
-
回溯:
-
sb
删除'a'
→"ab"
-
list
插回'a'
→['a']
-
-
-
-
-
回溯:
-
sb
删除'b'
→"a"
-
list
插回'b'
→['a', 'b']
-
-
-
-
-
回溯:
-
sb
删除'a'
→""
-
list
插回'a'
→['a', 'a', 'b']
-
-
-
-
第一层递归(i=1):
-
选择
list[1]='a'
(与i=0
的'a'
相同,但位置不同):-
过程类似,最终生成
"aab"
(重复,TreeSet
会自动去重)。
-
-
-
第一层递归(i=2):
-
选择
list[2]='b'
:-
list
移除'b'
→['a', 'a']
-
sb
追加'b'
→"b"
-
进入递归:
-
第二层递归(i=0):
-
选择
'a'
→"ba"
→ 递归 → 选择'a'
→"baa"
(加入set
)。
-
-
第二层递归(i=1):
-
选择另一个
'a'
→"ba"
→ 递归 → 选择'a'
→"baa"
(重复)。
-
-
-
-
最终结果
set = ["aab", "aba", "baa"]
(自动去重且按字典序排序)。
关键点
-
去重机制:
-
TreeSet
保证结果唯一且有序。 -
即使两个
'a'
相同,它们的排列仍会被生成,但重复结果会被Set
过滤。
-
-
回溯的核心操作:
-
选择:
list.remove(i)
+sb.append(ch)
。 -
撤销:
sb.deleteLastChar()
+list.add(i, ch)
。 -
确保每一层递归结束后,
list
和sb
恢复原状,才能继续尝试其他分支。
-
-
递归树示意图(以
"aab"
为例):Start: list=[a,a,b], sb="" | ├─ 选a → list=[a,b], sb="a" │ ├─ 选a → list=[b], sb="aa" │ │ └─ 选b → sb="aab" ✅ │ └─ 选b → list=[a], sb="ab" │ └─ 选a → sb="aba" ✅ └─ 选b → list=[a,a], sb="b" └─ 选a → list=[a], sb="ba" └─ 选a → sb="baa" ✅
通过这种逐步选择、递归、回溯的方式,代码能高效生成所有唯一排列。