题目
输入一个正整数数组,将数组里面所有的数字排列成一个数,打印出其中最小的一个。如输入{3,32,321},最小的为321323.
思路
全排列
题目最直接的解法就是将数组中的所有数字全排列,并把每个排列拼接起来,最后输出最小的数字。n个数字总共有n!种排列,其时间性能较差。同时在拼接比较过程中,需要当心大数问题。因此实现时不宜直接使用整形数进行直接比较,同时由于排列后的数据长度均相等,因此应该将其转化为字符串,通过对字符串的比较来确定最终的大小关系。
逐位比较
这种思路主要是从每个数字的高位开始,依次对每一位进行比较。当高位数字较小时,就应当排于前面,当高位数字相同时,依次比较次低位。此外当数字的位数不相等时,就将位数较短的数字的最后一位依次与其余数字比较,直到能够判定最终的排序关系为止。该种算法的时间性能为O(n^2)。
剑指offer中的解答
这种思路主要是定义一种字符串拼接之后的比较规则,通过对拼接字符串的比较来判断两个数字的前后关系,其具体的证明见《剑指offer》。其时间复杂度就等于排序算法的时间复杂度,为O(nlogn)。
代码实现
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
public class PrintMinNumCombineByArray
{
// 通过全排列比较输出最小值O(n!)
public static void printMinNum(int [] data)
{
if (null == data || 0 == data.length)
{
return ;
}
String [] str = new String[data.length];
for (int i = 0; i < data.length; ++i)
{
str[i] = String.valueOf(data[i]);
}
// 当然也可以直接用int型进行排列
printMinNum(str);
}
public static void printMinNum(String [] data)
{
if (null == data || 0 == data.length)
{
return ;
}
List<String> result = new LinkedList<>();
permutation(data, 0, result);
System.out.println(findMin(result));
}
private static void permutation(String [] data, int begin, List<String> list)
{
if (null == data || data.length == 0 || begin < 0 || begin >= data.length)
{
return ;
}
if (begin == data.length - 1)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.length; ++i)
{
sb.append(data[i]);
}
list.add(sb.toString());
}
for (int i = begin; i < data.length; ++i)
{
String tmp = data[begin];
data[begin] = data[i];
data[i] = tmp;
permutation(data, begin + 1, list);
tmp = data[i];
data[i] = data[begin];
data[begin] = tmp;
}
}
private static String findMin(List<String> list)
{
if (null == list)
{
return null;
}
StringBuilder minNum = new StringBuilder(list.get(0));
for (int i = 1; i < list.size(); ++i)
{
String num = list.get(i);
if (num.compareTo(minNum.toString()) < 0)
{
minNum.delete(0, minNum.length());
minNum.append(num);
}
}
return minNum.toString();
}
// 时间复杂度是O(n^2),逐位比较
public static void printMinNumByAno(int [] data)
{
if (null == data || data.length == 0)
{
return ;
}
List<Integer> list = new ArrayList<>();
for (int item : data)
{
list.add(item);
}
List<Integer> result = new LinkedList<>();
while (!list.isEmpty())
{
int index = 0;
int minBits = String.valueOf(list.get(index)).charAt(0) - '0';
for (int i = 1; i < list.size(); ++i)
{
String str = String.valueOf(list.get(i));
int bits = (int)str.charAt(0) - '0';
if (bits < minBits)
{
minBits = bits;
index = i;
}
else if (bits == minBits)
{
String minStr = String.valueOf(list.get(index));
int maxLen = str.length() > minStr.length() ? str.length() : minStr.length();
for (int k = 1; k < maxLen; ++k)
{
int sBits = str.charAt(((k > str.length() - 1) ? str.length() - 1 : k)) - '0';
int sMinBits = minStr.charAt(((k > minStr.length() - 1) ? minStr.length() - 1 : k)) - '0';
if (sBits == sMinBits)
{
continue;
}
else if (sBits > sMinBits)
{
break;
}
else
{
minBits = str.charAt(0) - '0';
index = i;
}
}
}
}
result.add(list.remove(index));
}
StringBuilder sb = new StringBuilder();
for (Integer item : result)
{
sb.append(item);
}
System.out.println(sb.toString());
}
// 这种方法的优势是得益于排序方式O(nlogn)
// 通过定义一个比较器,实现对字符串数组的比较排序
// 难于想到字符串排序的规则
public static void printMinNumByBook(int [] data)
{
if (null == data || data.length == 0)
{
return ;
}
String [] str = new String[data.length];
for (int i = 0; i < str.length; ++i)
{
str[i] = String.valueOf(data[i]);
}
Arrays.sort(str, new Comparator<String>()
{
public int compare(String o1, String o2)
{
StringBuilder sbFir = new StringBuilder(o1);
StringBuilder sbSec = new StringBuilder(o2);
sbFir.append(o2);
sbSec.append(o1);
return sbFir.toString().compareTo(sbSec.toString());
}
});
for (int i = 0; i < str.length; ++i)
{
System.out.print(str[i]);
}
System.out.println();
}
public static void main(String [] args)
{
// int [] data = {12, 123};
int [] data = {1, 123, 32, 435, 32212, 124, 222};
long startTime = System.currentTimeMillis();
printMinNumByAno(data);
System.out.println(System.currentTimeMillis() - startTime + "ms.");
startTime = System.currentTimeMillis();
printMinNumByBook(data);
System.out.println(System.currentTimeMillis() - startTime + "ms.");
startTime = System.currentTimeMillis();
printMinNum(data);
System.out.println(System.currentTimeMillis() - startTime + "ms.");
}
}
性能比较
在main()函数中测试了两种输入数组长度情况下的时间性能,分别为:
短数组
方式1 | 方式2 | 方式3 |
---|---|---|
2ms | 1ms | 0ms |
长数组
方式1 | 方式2 | 方式3 |
---|---|---|
2ms | 2ms | 64ms |
通过比较不难发现,在数组长度较短的情况下,通过全排列得出的结果时间性能较优,其余两种方式的时间性能较差;而在长数组的情况下,全排列方式的时间性能急剧下降;此外,也可以发现方式2的时间性能要由于方式1的。