38. 字符串的排列
1 题目描述
输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
2 题目分析
字符串的排列问题:全排列,考虑用回溯法,注意需要对重复元素去重(主要方法是对字符数组进行排序,保证每次加入的字符是重复字符中的最后一个)
在递归块中做如下判断
if (vis[j] || (j > 0 && vis[j - 1] && s[j - 1] == s[j])) continue;用于限制每次填入的字符一定是这个字符从左往右第一个未被填入的字符
backtrack(i, perm)表示当前排列为perm,下一个待填入的空位是第i个空位,perm初始为空字符串,递归函数分为两种情况:
1. 如果i==n,说明已经填完了n个空位,说明找到一个可行解,将perm放入答案数组中,递归结束。
2. 若果i<n,此时需要考虑第i个空位填哪个字符。定义一个标记数组vis来标记已经填过的字符,判断当前字符是否被标记过,没有则填入,继续尝试下一个空位,
然后回溯
思路二:
用分治法思路——把一个字符串看成由两部分组成:第一部分是它的第一个字符;第二部分是后面的所有字符。
在求解过程中也分成两步:
1. 求所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符交换
2. 固定第一个字符,同样方法求后面所有字符的排列。
3 代码
List<String> ans;
boolean[] vis;
public String[] permutation(String s) {
int n = s.length();
ans = new ArrayList<>();
char[] arr = s.toCharArray();
vis = new boolean[n];
// 排序:目的是去重
Arrays.sort(arr);
StringBuilder perm = new StringBuilder();
backtrack(arr, 0, n, perm);
return ans.toArray(new String[0]);
}
private void backtrack(char[] arr, int i, int n, StringBuilder perm) {
if (i == n) {
// 找到一个可行解,加入
ans.add(perm.toString());
return;
}
// 遍历
for (int j = 0; j < n; j++) {
// 判断
if (vis[j] || (j > 0 && !vis[j - 1] && arr[j - 1] == arr[j])) continue;
vis[j] = true;
perm.append(arr[j]);
backtrack(arr, i + 1, n, perm);
// 回溯
perm.deleteCharAt(perm.length() - 1);
vis[j] = false;
}
}
// 交换分治
Set<String> res = new HashSet<>();
public String[] permutation1(String s) {
int n = s.length();
char[] arr = s.toCharArray();
Arrays.sort(arr);
// 递归交换
permutation(arr, n, 0);
return res.toArray(new String[0]);
}
private void permutation(char[] arr, int n, int i) {
if (i == n) {
// 找到一个可行方案
// res.add(Arrays.toString(arr));
StringBuilder temp = new StringBuilder();
for (char c : arr) {
temp.append(c);
}
res.add(temp.toString());
return;
}
// 遍历
for (int j = i; j < n; j++) {
// 去重:不会,用set吧
/* if (j > 0 && arr[j - 1] == arr[j]) {
i++;
continue;
}*/
swap(arr, j, i);
permutation(arr, n, i + 1);
swap(arr, j, i);
}
}
public void swap(char[] arr, int a, int b) {
char temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}