全排列的几种算法
相关题目
字典序算法
public class Solution{
char[] charArray;
ArrayList<String> res;
/**
* 使用字典序算法
* @param str
* @return
*/
public ArrayList<String> Permutation(String str) {
charArray = str.toCharArray();
// 先按字典序排序
Arrays.sort(charArray);
res = new ArrayList<>();
res.add(String.valueOf(charArray));
while (nextString()){
res.add(String.valueOf(charArray));
}
return res;
}
/**
* 找到下一个排列
* @return 是否成功找到下一个排列
*/
private boolean nextString() {
int len = charArray.length;
int i = len - 2;
// 从右往左,找到第一个比它右一位的数小的数据,x
while (i >= 0 && charArray[i] >= charArray[i + 1])
i--;
// 当前字符串已是降序排列,证明所有排列已被找出
if(i == -1)
return false;
int j = len - 1;
// 从右往左,找到第一个比 x 大的数据
while (j >= 0 && charArray[j] <= charArray[i])
j--;
// 使用 >= 和 <=可以过滤掉重复的排列
// 交换
swap(i, j);
// 将i后面的数据按从小到大排序
sort(i + 1);
return true;
}
private void sort(int i) {
// 快速排序
quickSort(i, charArray.length - 1);
}
/**
* 快速排序
* @param low
* @param high
*/
private void quickSort(int low, int high) {
if(low >= high)
return;
char temp = charArray[low];
int l = low, r = high;
while (l < r){
while (l < r && charArray[r] > temp)
r--;
charArray[l] = charArray[r];
while (l < r && charArray[l] <= temp)
l++;
charArray[r] = charArray[l];
}
charArray[l] = temp;
quickSort(low, l - 1);
quickSort(l + 1, high);
}
/**
* 交换charArray[i]和charArray[j]
* @param i
* @param j
*/
public void swap(int i, int j){
char temp = charArray[i];
charArray[i] = charArray[j];
charArray[j] = temp;
}
}
回溯法(深度遍历 + 剪枝)
结果不要求排序
如果结果不要求排序,则可以使用原地交换的方法
class Solution {
// 返回结果字符串的List集合
private List<String> res = new LinkedList<>();
// 字符串s的字符数组sArray
private char[] sArray;
public String[] permutation(String s) {
sArray = s.toCharArray();
dfs(0);
return res.toArray(new String[0]);
}
/**
* 深度遍历 + 剪枝
* @param x 递归的层级,也是字符串当前固定的位置
*/
public void dfs(int x){
// 已经到了字符串的最后一个位置
if (x == sArray.length - 1) {
res.add(new String(sArray));
return;
}
// 在x位置上的字母的Set集合,不允许重复
Set<Character> cSet = new HashSet<>();
for (int i = x; i < sArray.length; i++){
// 如果出现重复字母,及时剪枝
if(cSet.contains(sArray[i]))
continue;
cSet.add(sArray[i]);
// 通过交换模拟排列
swap(i, x);
dfs(x + 1);
// 恢复sArray
swap(i, x);
}
}
/**
* 交换sArray[i]和sArray[j]
* @param i
* @param j
*/
public void swap(int i, int j){
char temp = sArray[i];
sArray[i] = sArray[j];
sArray[j] = temp;
}
}
结果要求排序
如果结果要求排序,原地交换会影响原本的排序状态,这时我们使用状态变量存储数据是否已在当前状态里。
class Solution {
char[] charArray;
ArrayList<String> res;
StringBuilder sb;
HashSet<Integer> visited; // 已经访问过的字符的下标
/**
* 使用深度遍历 + 剪枝,回溯算法
* @param str
* @return
*/
public ArrayList<String> Permutation(String str) {
charArray = str.toCharArray();
Arrays.sort(charArray);
res = new ArrayList<>();
visited = new HashSet<>();
sb = new StringBuilder();
dfs();
return res;
}
/**
* 交换charArray[i]和charArray[j]
* @param i
* @param j
*/
public void swap(int i, int j){
char temp = charArray[i];
charArray[i] = charArray[j];
charArray[j] = temp;
}
/**
* 深度遍历 + 剪枝
*/
private void dfs() {
int len = charArray.length;
if(sb.length() == len) {
res.add(sb.toString());
return;
}
// 当前位置已使用过的字母的集合
HashSet<Character> set = new HashSet<>();
for (int i = 0; i < len; i++){
if(set.contains(charArray[i]) || visited.contains(i))
continue;
visited.add(i);
sb.append(charArray[i]);
set.add(charArray[i]);
dfs();
// 剪枝
visited.remove(i);
sb.deleteCharAt(sb.length() - 1);
}
}
}