题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。


最容易想到的就是全排序后进行比较,然而肯定是要进行优化的,优化方式如下:对于数字m和n,可以拼接成mn和nm,如果mn<nm,则说明m应当放在n前面,哪怕还有其他的数也来拼接,都需要保证,所有的拼接方式中,m都在n的前面。
  以题目的条件为例,3和321拼接成3321和3213,说明了321一定在3之前,哪怕32最后插在了中间,也要保证这个规则,因此需要重新定义这个规则。而定义完这个规则后就应当进行排序,那么这里就和选择的排序算法有关了。比如思路1是使用了冒泡,而思路2使用的是工具类的排序方式。

思路1:

不调用任何工具类的原始方法。相当于在数组中进行了一次冒泡排序。每次将最大的都放到了数组最后面。根据不同的排序算法,这里有改进的空间。而排序规则就是将相邻的两个字符串合并并比较,将其中可能造成较大值的放在后面。

public class Solution {
    public String PrintMinNumber(int [] numbers) {
       StringBuilder sb= new StringBuilder();
        for (int i=0; i<numbers.length; i++){
            for (int j=i+1; j<numbers.length; j++){
                int a = Integer.valueOf(numbers[i]+""+numbers[j]);
                int b = Integer.valueOf(numbers[j]+""+numbers[i]);
                if (a > b){
                    int t = numbers[i];
                    numbers[i] = numbers[j];
                    numbers[j] = t;
                }               
            }
        }
        for (int i = 0; i < numbers.length; i++) {
             sb.append(numbers[i]);
        }
        return sb.toString();
    }
}

思路2:

这里使用的是实现Comparator接口的方式
  学习了大佬的代码后,改成了自己容易接受的风格,主要学习以下几个点,StringBuilder的使用,实现Comparator接口的匿名内部类的写法。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if(numbers==null || numbers.length<=0)
            return "";
        ArrayList<String> list = new ArrayList<String>();
        for(int i = 0; i < numbers.length; i++){
            list.add(String.valueOf(numbers[i]));
        }
        Collections.sort(list,new Comparator<String>(){
            @Override
            public int compare(String s1,String s2){
                String a=s1+s2;
                String b=s2+s1;
                return a.compareTo(b);
            }
        });
        StringBuilder sb= new StringBuilder();
        for(int i = 0; i < numbers.length; i++){
            sb.append(list.get(i));//将list集合中的元素添加到字符串中(list中已排序好)
        }
        return sb.toString();
    }
}
//有必要对核心的部分进行解释说明
//在整个Collection.sort方法运行后,完成了所有的比较过程,并且list中都是按照大小顺序摆放的了
//sort(List<T> list, Comparator<? super T> c) 根据某个比较器规则,对list进行排序
Collections.sort源码
 public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
//比较器只需要告诉我怎么比,然后sort会根据比较值的大小进行排序
//看起来代码中没有写list调整顺序的代码,实际上是由底层的sort完成了顺序的调整。

//这里匿名内部类的写法,变成 Collections.sort(list,new Comparator<String>(){内容});
//因此,重写了compare方法,定义比较规则,并在该规则中,使用的String类的compareTo方法
//compareTo(String anotherString)  按照字典序比较两个字符串。返回值是int 

总体上来看,这个方法很高大上,实际上不太容易在第一时间想出来,对我来说,需要的基础和注意的细节较多。比如String类的compareTo方法是按字典序进行排序,直接符合本题要求。否则,需要写一个比较字符串的方法。这题用字典序真的是很巧妙。


写法3:

根据其他人的方法,我自己改成了规则自定义,调用排序方法,哈哈,我真鸡贼。
这个写法,没有引入String类的比较方法,也就是说,不需要知道String类的compare方法的特性,只利用匿名内部类的方式,自定义比较方式。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if(numbers==null || numbers.length<=0)
            return "";
        ArrayList<Integer> list = new ArrayList<>();
        for(int i = 0; i < numbers.length; i++){
            list.add(numbers[i]);
        }
        Collections.sort(list,new Comparator<Integer>(){
            @Override
            public int compare(Integer s1,Integer s2){
                int a = Integer.valueOf(s1+""+s2);
                int b = Integer.valueOf(s2+""+s1);
                return a-b;//这里如果是b-a则是从大到小的排序了
            }
        });
        StringBuilder sb= new StringBuilder();
        for(int i = 0; i < numbers.length; i++){
            sb.append(list.get(i));//将list集合中的元素添加到字符串中(list中已排序好)
        }
        return sb.toString();
    }
}

不是总结的体会:刚开始是随机刷题,难度太大,后来按照offer书上的顺序刷题,发现随着题目的难度的上升,大佬们越来越多的开始使用一些封装好的工具进行解题。原来有些题可能直接调用工具就解出了,现在这些工具只是解题的一部分,不知道最后的笔试和面试对工具类的使用如何定义。大概是题目越复杂,越允许使用工具吧,也可能是根据考察的点,比如如何实现,简单题问你思路,你答个工具实现就不太合适了。
总结:第1种方式是最原始的,排序和定义比较规则都是自己写,第3种写法是稍微讨巧的方式,是排序调用工具类,比较规则自己写。而第2种写法是大佬级别的写法,基础好,排序也调用,规则也直接引用的现成的方法(CompareTo方法)。