题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
思路:
看题目的意思,应该就是求一个全排列?
想到一个递归的方法,先把原字符串分割,split(),放入一个char数组。每次递归传入当前的StringBuilder对象和已使用的下标flag数组,可以防止重复使用,当对象的length等于字符串长度后,转换成String加入到结果数组中。
在本地跑是正常的,在OJ系统上提示
测试用例:
a
对应输出应该为:
[“a”]
你的输出为:
java.lang.StringIndexOutOfBoundsException: String index out of range: -1
但这个用例本地也是正常的。
查阅相关资料后发现是split的问题。
网上说split的使用尽量都用转义字符表示。
在java.lang包中有String.split()方法,返回是一个数组: 1、如果用“.”作为分隔的话,必须是如下写法:String.split("\\."),这样才能正确的分隔开,不能用String.split(".");
2、如果用“|”作为分隔的话,必须是如下写法:String.split("\\|"),这样才能正确的分隔开,不能用String.split("|"); “.”和“|”都是转义字符,必须得加"\\";
3、如果在一个字符串中有多个分隔符,可以用“|”作为连字符,比如:“acount=? and uu =? or n=?”,把三个都分隔出来,可以用String.split("and|or");
这里只是把String变成字符数组,还是用自带的toCharArray()吧。
因为题目中有提到字典序,所以最后还要Collections.sort(result);
这样即使一开始输入的字符串并不是字典序,最后也会变成字典序。
我的代码如下(这个递归的思路更好理解一点)
import java.util.ArrayList;
public class Solution {
ArrayList result = new ArrayList<>();
public ArrayList Permutation(String str) {
if(str==null||str.length()==0)
return result;
String[] a = str.trim().split(""); // toCharArray()
int length = a.length;
int[] flag = new int[length];
StringBuilder sb = new StringBuilder();
helper(sb,flag,a);
return result;
}
public void helper(StringBuilder sb, int[] flag, String[] a){
if(sb.length()==flag.length&&!result.contains(sb.toString())){
result.add(sb.toString());
return;
}
for(int i = 0; iif(flag[i]==1)
continue;
sb.append(a[i]);
flag[i] = 1;
helper(sb,flag,a);
flag[i] = 0;
sb.deleteCharAt(sb.length()-1);
}
}
}
解决问题 提交时间 状态 运行时间 占用内存 使用语言
字符串的排列 2017-06-20 答案正确 208 ms 12284K Java
另一种递归思路,将元素交换求全排列。
这个方法必须要加Collection.sort(),即使在输入str是有序的时候也必须进行这一步。
因为一开始是存在了HashSet里,HashSet里的元素是无序的。(不保证输出顺序和输入顺序相同)
public ArrayList Permutation(String str) {
ArrayList re = new ArrayList<>();
if (str == null || str.length() == 0) {
return re;
}
HashSet set = new HashSet();
fun(set, str.toCharArray(), 0);
re.addAll(set);
Collections.sort(re);
return re;
}
void fun(HashSet re, char[] str, int k) {
if (k == str.length) {
re.add(new String(str));
return;
}
for (int i = k; i < str.length; i++) {
swap(str, i, k);
fun(re, str, k + 1);
swap(str, i, k);
}
}
void swap(char[] str, int i, int j) {
if (i != j) {
char t = str[i];
str[i] = str[j];
str[j] = t;
}
}
https://jingyan.baidu.com/article/86112f1329e97927379787da.html(用这个图看递归很好,学习!)
非递归的生成字典序方法,目前执行时间最少
import java.util.*;
public class Solution{
public ArrayList Permutation(String str) {
ArrayList res = new ArrayList<>();
if (str != null && str.length() > 0) {
char[] seq = str.toCharArray();
Arrays.sort(seq); //排列
res.add(String.valueOf(seq)); //先输出一个解
int len = seq.length;
while (true) {
int p = len - 1, q;
//从后向前找一个seq[p - 1] < seq[p]
while (p >= 1 && seq[p - 1] >= seq[p]) --p;
if (p == 0) break; //已经是“最小”的排列,退出
//从p向后找最后一个比seq[p]大的数
q = p; --p;
while (q < len && seq[q] > seq[p]) q++;
--q;
//交换这两个位置上的值
swap(seq, q, p);
//将p之后的序列倒序排列
reverse(seq, p + 1);
res.add(String.valueOf(seq));
}
}
return res;
}
public static void reverse(char[] seq, int start) {
int len;
if(seq == null || (len = seq.length) <= start)
return;
for (int i = 0; i < ((len - start) >> 1); i++) {
int p = start + i, q = len - 1 - i;
if (p != q)
swap(seq, p, q);
}
}
public static void swap(char[] cs, int i, int j) {
char temp = cs[i];
cs[i] = cs[j];
cs[j] = temp;
}
}
如果给的原字符串中有重复的字符,虽然用了set结构可以防止重复放入结果集,但是也可以通过代码去重,在for循环中加入一个判断即可,如果测试用例中重复字符很多,可以减小时间复杂度
static boolean is_swap(char[] s, int i, int k)
{
if (i == k) // 自己和自己交换是允许的
return true;
for (int j = i; j < k; j++) //保证在i到k的中间,不存在和k相同的字符,如果有的话,说明k重复了,这次交换没有必要
{
if (s[j] == s[k])
return false;
}
return true;
}
return true;
}