题目地址:
https://www.lintcode.com/problem/string-permutation-ii/description
给定一个字符串,求其所有字符的全排列,重复的排列只返回一次。
思路是DFS。由于元素有重复,所以如果直接暴力排列的话,会出现同一种排列出现了很多次的情况,所以难点就在于怎样避免重复。避免的重复的方法是,我们人为规定一个条件,满足这个条件的排列,加入最后的结果中,而对于不满足这个条件的排列,及时略过。为了方便起见,我们先将字符串的字符数组排序,使得相同字符紧邻,然后我们认为规定,相同字符的取法永远都是从左向右连续的取。
具体来说,如果字符串含连续的三个 a a a,分别标记为 a 1 a 2 a 3 a_1a_2a_3 a1a2a3,那么对于某个排列来说,我们只允许取 a 1 a_1 a1, a 1 a 2 a_1a_2 a1a2和 a 1 a 2 a 3 a_1a_2a_3 a1a2a3,不允许标号大的排列在标号小的之前,也不允许跳着取。也就是说,我们遇到这样的情况,就需要跳过去,即循环到 i i i的时候, a i = a i − 1 a_i=a_{i-1} ai=ai−1,但 a i − 1 a_{i-1} ai−1没取过,则此次 i i i需要跳过。这样一来,很显然,第一个 a a a只能取 a 1 a_1 a1,否则就会导致取了 a i a_i ai但是 a i − 1 a_{i-1} ai−1没取,那么这样的排列就会略过。同时,也不会产生跳着取的情况,一旦跳着取,中间会有个”漏了“的元素没取,也会略过。代码如下:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
/**
* @param str: A string
* @return: all permutations
*/
public List<String> stringPermutation2(String str) {
// write your code here
List<String> res = new ArrayList<>();
if (str == null || str.isEmpty()) {
res.add("");
return res;
}
char[] s = str.toCharArray();
// 为了使得相同字母挨在一起,需要先排列
Arrays.sort(s);
boolean[] used = new boolean[s.length];
dfs(s, new StringBuilder(), used, res);
return res;
}
// sb是已经取好的字符,used标记sb里取好的字符
private void dfs(char[] s, StringBuilder sb, boolean[] used, List<String> res) {
// 如果取好了所有字符,则加入res
if (sb.length() == s.length) {
res.add(sb.toString());
return;
}
// 接着从第0位开始枚举下一个要加入sb的字符
for (int i = 0; i < s.length; i++) {
// 如果第i位已经取过,则跳过;如果第i位和第i-1位相同,但第i-1位没取过,
// 说明要么相同字符已经取到了第一个之后的那个字符,此时要跳过,
// 要么存在跳着取的情况,也需要跳过
if (used[i] || (i > 0 && s[i] == s[i - 1] && !used[i - 1])) {
continue;
}
sb.append(s[i]);
used[i] = true;
dfs(s, sb, used, res);
used[i] = false;
sb.setLegnth(sb.length() - 1);
}
}
}
时间复杂度 O ( n n ! ) O(nn!) O(nn!)( n n n为字符串长度),空间 O ( n ) O(n) O(n)。