给定一组非负整数,重新排列每个数的顺序,使之组成一个最大的整数
排序 + 自定义比较规则
首先考虑最简单的情况,只有2个数,a
和b
。我们只需要判断把哪个数放在前面就行了。
只要把所有数都按照这样的前后顺序摆好,得到的就是能组成的最大的数。
其实就是排序。但我们需要自定义比较规则。
这样来定义a
和b
的大小关系:
a
放在前面,组成的数更大时。我们定义此时a < b
a
放在后面,组成的数更大时。我们定义此时a > b
那么,我们只需要按照上述定义的大小关系,对整个数组进行一次排序,然后依次把每个数取出来,组成的数就是一个最大的数。
那么,这道题的核心点就是,判断a
和b
之间的大小关系的函数,要怎么写。
我们先把算法的整体框架写出来(手写一个快排)。
代码如下
class Solution {
public String largestNumber(int[] nums) {
quickSort(nums, 0, nums.length - 1);
StringBuilder sb = new StringBuilder();
for (int x : nums) sb.append(x);
// 去除前导0
while (sb.length() > 1 && sb.charAt(0) == '0') sb.deleteCharAt(0);
return sb.toString();
}
private void quickSort(int[] nums, int l, int r) {
if (l >= r) return;
int x = nums[l + r >> 1], i = l - 1, j = r + 1;
while (i < j) {
do i++; while (nums[i] < x); // TODO 这里比较 nums[i] 和 x 的大小, 后续换成自定义的函数
do j--; while (nums[j] > x); // TODO 这里比较 nums[i] 和 x 的大小, 后续换成自定义的函数
if (i < j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
quickSort(nums, l, j);
quickSort(nums, j + 1, r);
}
}
然后再来想想,如何实现比较2个数大小的函数
我们从a
和b
的最高位开始,依次比较每一位上的数字
a
和b
都没有用完所有的位,就比较出了结果
假设a
和b
,在某一位上的数字不同,则把较大的那个数,放在前面即可
比如9
和50
,在第一位上发现9 > 5
,则要把9
放在前面
再比如63723
和6381
,在第三位上,8 > 7
,所以6381
放前面
需要放在前面的数,在我们的定义中就是较小的数,放在后面的数,是较大的数
有一个数用完了所有的位,还未比较出结果,则要换一种方式
a
的部分已经用完了,我们要继续比较b
超出的部分。怎么比呢?根据上面的图,可以发现。
我们设j
为b
超出部分的第一位(在这个例子中是第四位,下标为3
),设i
为b
的最高位(下标为0
)
发现位置j
的数,是9
,位置i
的数,是6
,而9 > 6
,则说明b
要放在前面,才能使得组成的数更大。所以b
应该为较小的数。
上面这个例子还可以扩展,若比较j
和i
发现相等,怎么办?那就要把j
和i
各自后移一位,继续比较。这个过程中,注意到j
是有可能到达b
的末尾的,此时b
已经没有数了,此时要换a
,来继续比较。比如下面这个例子
a
=636
,b
=63663
我们要比较到a
的第二位,才能得出结果。
需要注意处理这种边界情况。
对于a
超出的情况,同理。
我们写出这个比较函数如下
/**
* @return 1 when a > b , 0 when a = b, -1 when a < b
* **/
private static int compare(int a, int b) {
int[] bitsA = new int[10]; // 数最大值为 10^9 , 所以最多10位
int[] bitsB = new int[10];
int lastA = -1, lastB = -1; // 计数
// 取出a和b的每一位
// 用 do while 循环, 以便兼容 a = 0 的情况
do {
bitsA[++lastA] = a % 10;
a /= 10;
} while (a > 0);
do {
bitsB[++lastB] = b % 10;
b /= 10;
} while (b > 0);
// 从2个数的最高位开始进行比较
// 注意最高位是数组的最后一个位置
int i = lastA, j = lastB;
while (i >= 0 && j >= 0) {
// 当2个数都没用完所有的位
if (bitsA[i] > bitsB[j]) return -1; // a应该放在前面, 所以a更小, a < b
if (bitsA[i] < bitsB[j]) return 1; // b应该放在前面, 所以b更小, a > b
i--;
j--;
}
// 有一个数已经用完了所有的位
if (j >= 0) {
// 当b还有位置时
int bCur = j, bStart = lastB; // 从当前位置和b的第一个位置开始比较
while (bCur >= 0) {
if (bitsB[bCur] > bitsB[bStart]) return 1; // b应该放在前面, b更小
if (bitsB[bCur] < bitsB[bStart]) return -1; // b应该放在后面, b更大
bCur--;
bStart--;
}
// b用完了, 该用a了
int aStart = lastA;
while (bStart >= 0) {
if (bitsA[aStart] < bitsB[bStart]) return -1; //b应该放后面
if (bitsA[aStart] > bitsB[bStart]) return 1; //b应该放在前面
aStart--;
bStart--;
}
return 0; // 一样大
} else {
// 当a还有位置时, 把上面代码复制过来改改即可
int aCur = i, aStart = lastA;
while (aCur >= 0) {
if (bitsA[aCur] > bitsA[aStart]) return -1; //a放前面
if (bitsA[aCur] < bitsA[aStart]) return 1; // a放后面
aCur--;
aStart--;
}
int bStart = lastB;
while (aStart >= 0) {
if (bitsB[bStart] < bitsA[aStart]) return 1; // a放后面
if (bitsB[bStart] > bitsA[aStart]) return -1;
bStart--;
aStart--;
}
return 0;
}
}
简单测试一下这个函数是否符合我们的预期,用上面提到的几个数据
public static void main(String[] args) {
int[][] testCases = new int[][]{{9, 50}, {63723, 6381}, {637, 63798}, {636, 63663}};
for (int[] c : testCases) {
int a = c[0];
int b = c[1];
int res = compare(a, b);
System.out.printf("a = %d, b = %d ", a, b);
if (res > 0) System.out.printf("a > b, res = %d%d\n", b ,a);
else if (res < 0) System.out.printf("a < b , res = %d%d\n", a, b);
else System.out.printf("a = b, res = %d%d\n", a, b);
}
}
那么应该没问题了,现在把算法框架中比较2个数大小的地方,改成调用这个函数
class Solution {
public String largestNumber(int[] nums) {
quickSort(nums, 0, nums.length - 1);
StringBuilder sb = new StringBuilder();
for (int x : nums) sb.append(x);
// 去除前导零
while (sb.length() > 1 && sb.charAt(0) == '0') sb.deleteCharAt(0);
return sb.toString();
}
private void quickSort(int[] nums, int l, int r) {
if (l >= r) return;
int x = nums[l + r >> 1], i = l - 1, j = r + 1;
while (i < j) {
do i++; while (compare(nums[i], x) < 0); // 比较大小改为调用自定义的函数
do j--; while (compare(nums[j], x) > 0); // 比较大小改为调用自定义的函数
if (i < j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
quickSort(nums, l, j);
quickSort(nums, j + 1, r);
}
/**
* @return 1 when a > b , 0 when a = b, -1 when a < b
* **/
private int compare(int a, int b) {
int[] bitsA = new int[10]; // 数最大值为 10^9 , 所以最多10位
int[] bitsB = new int[10];
int lastA = -1, lastB = -1; // 计数
// 取出a和b的每一位
// 用 do while 循环, 以便兼容 a = 0 的情况
do {
bitsA[++lastA] = a % 10;
a /= 10;
} while (a > 0);
do {
bitsB[++lastB] = b % 10;
b /= 10;
} while (b > 0);
// 从2个数的最高位开始进行比较
// 注意最高位是数组的最后一个位置
int i = lastA, j = lastB;
while (i >= 0 && j >= 0) {
// 当2个数都没用完所有的位
if (bitsA[i] > bitsB[j]) return -1; // a应该放在前面, 所以a更小, a < b
if (bitsA[i] < bitsB[j]) return 1; // b应该放在前面, 所以b更小, a > b
i--;
j--;
}
// 有一个数已经用完了所有的位
if (j >= 0) {
// 当b还有位置时
int bCur = j, bStart = lastB; // 从当前位置和b的第一个位置开始比较
while (bCur >= 0) {
if (bitsB[bCur] > bitsB[bStart]) return 1; // b应该放在前面, b更小
if (bitsB[bCur] < bitsB[bStart]) return -1; // b应该放在后面, b更大
bCur--;
bStart--;
}
// b用完了, 该用a了
int aStart = lastA;
while (bStart >= 0) {
if (bitsA[aStart] < bitsB[bStart]) return -1; //b应该放后面
if (bitsA[aStart] > bitsB[bStart]) return 1; //b应该放在前面
aStart--;
bStart--;
}
return 0; // 一样大
} else {
// 当a还有位置时, 把上面代码复制过来改改即可
int aCur = i, aStart = lastA;
while (aCur >= 0) {
if (bitsA[aCur] > bitsA[aStart]) return -1; //a放前面
if (bitsA[aCur] < bitsA[aStart]) return 1; // a放后面
aCur--;
aStart--;
}
int bStart = lastB;
while (aStart >= 0) {
if (bitsB[bStart] < bitsA[aStart]) return 1; // a放后面
if (bitsB[bStart] > bitsA[aStart]) return -1;
bStart--;
aStart--;
}
return 0;
}
}
}
上面这个自定义的比较规则,实现起来有些复杂(因为是纯手工模拟按位比较的过程)。我们可以换个稍微简单点的方式。只要把ab
和ba
两种组合的数字算出来,再比较一下大小就行啦~下面提供2种实现思路:
long
存储,否则会溢出)使用数字计算:
/**
* @return 1 when a > b , 0 when a = b, -1 when a < b
* **/
private int compare(int a, int b) {
long pow10a = 1, pow10b = 1;
int ta = a, tb = b;
do {
pow10a *= 10;
ta /= 10;
} while (ta > 0);
do {
pow10b *= 10;
tb /= 10;
} while (tb > 0);
long ab = a * pow10b + b;
long ba = b * pow10a + a;
if (ab > ba) return -1; // a应该放前面, a小
if (ab < ba) return 1;
return 0;
}
使用字符串
/**
* @return 1 when a > b , 0 when a = b, -1 when a < b
* **/
private int compare(int a, int b) {
String sa = String.valueOf(a);
String sb = String.valueOf(b);
String ab = sa + sb;
String ba = sb + sa;
if (ab.compareTo(ba) > 0) return -1; // ab更大, a放前面, a小
if (ab.compareTo(ba) < 0) return 1;
return 0;
}