Given a string, sort it in decreasing order based on the frequency of characters.
Example 1:
Input: "tree" Output: "eert" Explanation: 'e' appears twice while 'r' and 't' both appear once. So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.
Example 2:
Input: "cccaaa" Output: "cccaaa" Explanation: Both 'c' and 'a' appear three times, so "aaaccc" is also a valid answer. Note that "cacaca" is incorrect, as the same characters must be together.
Example 3:
Input: "Aabb" Output: "bbAa" Explanation: "bbaA" is also a valid answer, but "Aabb" is incorrect. Note that 'A' and 'a' are treated as two different characters.这道题,我的思路就是得到字符串中每个字符出现的次数,然后从大到小排序,再拼接字符串。为了提升速度,从大到小排序我用了快排,结果发现还是超出了时间限制,非常气愤,就去看大家怎么搞的,发现大家都使用了以空间换时间的bucket sort,这个排序大部分情况下能胜过快排。然后我就照着大家的思路写了一种解法:先得到每个字符出现的次数,然后桶排序次数,桶越往后,对应的次数越大,最后从最后的桶开始由后往前拼接字符串。
按道理这样写代码,我的思路解法都跟大家的一样,最后结果也应该和大家一样AC了,可是发现还是TLE了,为啥呢,我就打了几个断点算时间,如下代码所示(是我的代码):
public class Solution {
public String frequencySort(String s) {
long time1=System.currentTimeMillis();
String result="";
int[] map=new int[128];
char[] chars=s.toCharArray();
int max=0;
for(int i=0;i<chars.length;i++){
map[chars[i]]++;
max=map[chars[i]]>max?map[chars[i]]:max;
}
long time2=System.currentTimeMillis();
String[] bucket=new String[max+1];
for(int i=0;i<128;i++){
int num=map[i];
if(num>0){
if(bucket[num]==null){
bucket[num]=(char)i+"";
}
else{
bucket[num]=bucket[num]+(char)i;
}
}
}
long time3=System.currentTimeMillis();
for(int i=max;i>=1;i--){
String theString=bucket[i];
if(theString!=null){
char[] thisChars=theString.toCharArray();
for(int j=0;j<thisChars.length;j++){
for(int k=0;k<i;k++){
result=result+thisChars[j];
}
}
}
}
long time4=System.currentTimeMillis();
float excTime1=(float)(time2-time1)/1000;
float excTime2=(float)(time3-time2)/1000;
float excTime3=(float)(time4-time3)/1000;
System.out.println("执行时间1:"+excTime1+"s");
System.out.println("执行时间2:"+excTime2+"s");
System.out.println("执行时间3:"+excTime3+"s");
return result;
}
}
然后发现excTime1和excTime2都在0.04内,只有excTime3居然飙到了1秒多,然后我看了看别人的解法:
public String frequencySort(String s) {
if(s.length() < 3)
return s;
int max = 0;
int[] map = new int[256];
for(char ch : s.toCharArray()) {
map[ch]++;
max = Math.max(max,map[ch]);
}
String[] buckets = new String[max + 1]; // create max buckets
for(int i = 0 ; i < 256; i++) { // join chars in the same bucket
String str = buckets[map[i]];
if(map[i] > 0)
buckets[map[i]] = (str == null) ? "" + (char)i : (str + (char) i);
}
StringBuilder strb = new StringBuilder();
for(int i = max; i >= 0; i--) { // create string for each bucket.
if(buckets[i] != null)
for(char ch : buckets[i].toCharArray())
for(int j = 0; j < i; j++)
strb.append(ch);
}
return strb.toString();
}
第三部分用的是StringBuilder!原来String和StringBuilder拼接字符串性能相差是云泥之别啊!而且经过查阅资料我得知:StringBuilder比StringBuffer运行速度要快,因为StringBuilder是针对于单线程的,所以它是非线程安全的,普通情况下建议使用StringBuilder。
对于三者使用的总结: 1.如果要操作少量的数据用 = String
2.单线程操作字符串缓冲区下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区下操作大量数据 = StringBuffer
然后我就听话地换成了StringBuilder,果然就AC了,而且还beat了96%的java submissions...
另外,关于bucket sort,请看我下一篇转载的文章。
之后我又想到,我之前用快排TLE了,会不会也是因为String拼接太慢的原因呢?然后我换成了StringBuilder,果然AC了!并且beat了90%的java submissions,这样看来确实bucket sort比快排要快啊。使用快排的代码如下所示,其中all数组记录了所有ascii码的字符出现的次数,index为ascii码。因为快排后index会改变,所以引入一个biaoji数组来记录每个位置对应的ascii码。
public class Solution {
public String frequencySort(String s) {
int[] biaoji=new int[127];
int[] all=new int[127];
char[] charArray=s.toCharArray();
for(int i=0;i<biaoji.length;i++){
biaoji[i]=i;
}
for(int i=0;i<charArray.length;i++){
int charIndex=charArray[i];
all[charIndex]++;
}
kuaipai(all,biaoji, 0, all.length-1);
StringBuffer sb=new StringBuffer();
for(int i=0;i<biaoji.length;i++){
char a=(char)(biaoji[i]);
if(all[i]==0){
break;
}
for(int j=0;j<all[i];j++){
sb.append(a);
}
}
return sb.toString();
}
public void kuaipai(int[] all,int[] biaoji,int left,int right){
if (left < right) {
int low = left;
int high = right;
int pivot = all[low];
int biaojipivot=biaoji[low];
while (low < high) {
while (low < high && all[high] <= pivot) {
high--;
}
if(low<high){
biaoji[low] = biaoji[high];
all[low] = all[high];
low++;
}
while (low < high && all[low] >= pivot) {
low++;
}
if(low<high){
biaoji[high] = biaoji[low];
all[high] = all[low];
high--;
}
}
biaoji[low] = biaojipivot;
all[low] = pivot;
kuaipai(all, biaoji, left, low - 1);
kuaipai(all, biaoji, high + 1, right);
}
}
}
其他大神的想法类似,都是三步曲,只不过第一步换成了hashmap来统计每个字符的出现次数。
1.构建一个map,统计每个字符出现的次数。
2.创建一个数组,使得这个数组的Index来表达字符出现的个数。Build a map of characters to the number of times it occurs in the string
3.从后往前遍历该数组,在每个index处,把该位置的字符加index次到结果字符串上。
public String frequencySort(String s) {
if (s == null) {
return null;
}
Map<Character, Integer> map = new HashMap();
char[] charArray = s.toCharArray();
int max = 0;
for (Character c : charArray) {
if (!map.containsKey(c)) {
map.put(c, 0);
}
map.put(c, map.get(c) + 1);
max = Math.max(max, map.get(c));
}
List<Character>[] array = buildArray(map, max);
return buildString(array);
}
private List<Character>[] buildArray(Map<Character, Integer> map, int maxCount) {
List<Character>[] array = new List[maxCount + 1];
for (Character c : map.keySet()) {
int count = map.get(c);
if (array[count] == null) {
array[count] = new ArrayList();
}
array[count].add(c);
}
return array;
}
private String buildString(List<Character>[] array) {
StringBuilder sb = new StringBuilder();
for (int i = array.length - 1; i > 0; i--) {
List<Character> list = array[i];
if (list != null) {
for (Character c : list) {
for (int j = 0; j < i; j++) {
sb.append(c);
}
}
}
}
return sb.toString();
}