输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
这道题思路明显,全排列问题的解决核心就是利用回溯法。只不过这道题全排列的结果可能会包含重复的值,所以还需要应用剪枝来去重,所以下面的几种解法都是利用回溯法思想,只不过剪枝的操作略有不同。
具体的多种解法见下:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> list1 = new ArrayList<String>();
if(str.length()==0){
return list1;
}
List<List<String>> list = new ArrayList<>();
boolean flag[]=new boolean[str.length()];
String[] strings = str.split("");
Stack<String> stack = new Stack<String>();
quanpailie(strings,flag,0,strings.length,list,stack);
for(int i=0;i<list.size();i++){
String temp = list.get(i).toString();
String[] temp1 = temp.substring(1,temp.length()-1).split(", ");
System.out.println(temp1.length);
StringBuffer SB = new StringBuffer();
for(String ss:temp1){
SB.append(ss);
}
if(!list1.contains(SB.toString())){
//System.out.println(SB.toString().length());
list1.add(SB.toString());
}
}
return list1;
}
public void quanpailie(String[] str, boolean[] flag, int cursize, int len, List<List<String>> list, Stack<String> stack){//这里的回溯算法入口的参数过多了,可以设置为私有变量private,从而不需要传入这么多的入参。
if(cursize==len){
list.add(new ArrayList<String>(stack));
return;
}
else {
for(int i=0;i<len;i++){
if(!flag[i]){
stack.push(str[i]);
flag[i]=true;
quanpailie(str,flag,cursize+1,len,list,stack);
flag[i]=false;//状态重置
stack.pop();//状态重置
}
}
}
}
}
可以明显看出,上面这个代码非常的臃肿繁杂,简化一下:
private boolean flag[]=new boolean[9];
private List<List<String>> list = new ArrayList<>();
private Stack<String> stack = new Stack<String>();
public ArrayList<String> Permutation(String str) {
ArrayList<String> list1 = new ArrayList<String>();
if(str.length()==0){
return list1;
}
//List> list = new ArrayList<>();
//boolean flag[]=new boolean[str.length()];
String[] strings = str.split("");
//Stack stack = new Stack();
quanpailie(strings,0,strings.length);
for(int i=0;i<list.size();i++){
String temp = list.get(i).toString();
String[] temp1 = temp.substring(1,temp.length()-1).split(", ");
System.out.println(temp1.length);
StringBuffer SB = new StringBuffer();
for(String ss:temp1){
SB.append(ss);
}
if(!list1.contains(SB.toString())){
//System.out.println(SB.toString().length());
list1.add(SB.toString());
}
}
return list1;
}
public void quanpailie(String[] str, int cursize, int len){
if(cursize==len){
list.add(new ArrayList<String>(stack));
return;
}
else {
for(int i=0;i<len;i++){
if(!flag[i]){
stack.push(str[i]);
flag[i]=true;
quanpailie(str,cursize+1,len);
flag[i]=false;//状态重置
stack.pop();//状态重置
}
}
}
}
进一步简化:
private boolean flag[]=new boolean[9];
private ArrayList<String> list = new ArrayList<>();
private StringBuffer sb = new StringBuffer();
public ArrayList<String> Permutation(String str) {
if(str.length()==0){
return list;
}
String[] strings = str.split("");
quanpailie(strings,0,strings.length);
return list;
}
//最核心的就是回溯函数(本质就是递归)以及状态的重置
public void quanpailie(String[] str, int cursize, int len){
if(cursize==len){
if(!list.contains(sb.toString())){//去重(剪枝),时间复杂度过高,在leetcode会超时
list.add(sb.toString());
}
return;
}
else {
for(int i=0;i<len;i++){
if(!flag[i]){
sb.append(str[i]);
flag[i]=true;
quanpailie(str,cursize+1,len);
flag[i]=false;//状态重置
sb.deleteCharAt(sb.length()-1);//状态重置
}
}
}
}
针对上面剪枝的时间复杂度过高的问题,继续优化如下,利用TreeSet完成去重操作:
class Solution {
private TreeSet<String> set = new TreeSet<>();//利用TreeSet执行去重效果,因为其本身就不支持重复的元素
private StringBuffer sb = new StringBuffer();
private boolean flag[] = new boolean[9];
public String[] permutation(String s) {
String[] strings = s.split("");
if(s==null || s.length()==0){
return strings;
}
quanpailie(strings,0,strings.length);
String [] res = new String[set.size()];
int i=0;
for(String s1:set){
res[i]=s1;
i++;
}
return res;
}
public void quanpailie(String[] strings, int current, int len){
if(current==len){
set.add(sb.toString());
return;
}
else {
for(int i=0;i<len;i++){
if(!flag[i]){
sb.append(strings[i]);
flag[i]=true;
quanpailie(strings,current+1,len);
flag[i]=false;
sb.deleteCharAt(sb.length()-1);
}
}
}
}
}
还可以继续优化,在回溯的期间完成剪枝操作:
//上面两种去重操作的时间复杂度都有一些大,这里先将数组排序,然后在添加的时候判断,时间复杂度低一些
class Solution {
private ArrayList<String> list = new ArrayList<>();
private StringBuffer sb = new StringBuffer();
private boolean flag[] = new boolean[9];
public String[] permutation(String s) {
String[] strings = s.split("");
char [] c = s.toCharArray();
Arrays.sort(c);//这里的排序操作是为了后面的去重
if(s==null || s.length()==0){
return strings;
}
quanpailie(c,0,c.length);
String [] res = new String[list.size()];
int i=0;
for(String s1:list){
res[i]=s1;
i++;
}
return res;
}
public void quanpailie(char[] c, int current, int len){
if(current==len){
list.add(sb.toString());
return;
}
else {
for(int i=0;i<len;i++){
if(!flag[i]){
if(i>0 && c[i]==c[i-1] && !flag[i-1]){//去重操作(剪枝操作)
continue;
}
sb.append(c[i]);
flag[i]=true;
quanpailie(c,current+1,len);
flag[i]=false;
sb.deleteCharAt(sb.length()-1);
}
}
}
}
}