最大数
约 1607 字大约 5 分钟
2025-02-23
179. 最大数
给定一组非负整数 nums
,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
示例 1:
输入:nums = [10,2]
输出:"210"
示例 2:
输入:nums = [3,30,34,5,9]
输出:"9534330"
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 10^9
调用排序函数,巧定排序规则
步骤:
- 将整数数组 nums 转换成字符串数组strs,因为后续需要进行字符串的拼接和比较操作。
- 巧定排序规则,在 Arrays.sort(strs, (x,y) -> f(x,y)) 中,如果 f(x,y) > 0,则说明 x > y,即 y 在 x 的前面。因此为了让大数在前面,我们需要设计一个映射f, 满足当 x < y 时,f(x,y) > 0,很明显,我们可以用 f(x,y) = y - x,但是x和y是字符串,不能这么做,所以我们只能用 f(x,y) = (y+x).compareTo(x+y)。
- 排完序后,要检查第一个元素是否为"0",如果是,则整个数组都是0,直接返回"0"。否则把字符数组拼接成字符串并返回。
细节:
- 判空
- 结果数组可能为 0,这时候直接返回一个 0,不要拼接多个 0.
class Solution {
public String largestNumber(int[] nums) {
// 将整数数组nums转换成字符串数组strs,因为后续需要进行字符串的拼接和比较操作。
String[] strs = new String[nums.length];
for (int i = 0; i < nums.length; i++)
strs[i] = String.valueOf(nums[i]);
// 将两个字符串x和y进行拼接(即y+x和x+y),然后比较这两个拼接后的字符串。如果y+x比x+y大,
// 说明在最终组合的数字中,y应该放在x的前面,即把大的放在前面,这样排序后的数组能够组成最大的数字。
// 如果(y + x).compareTo(x + y)的结果大于0,则意味着字符串yx的字典序大于xy的字典序,
// 按照这个比较器的定义,x>y,因此y应该排在x之前。
// 默认升序,所以如果x>y,则y在x前。
Arrays.sort(strs, (x, y) -> (y + x).compareTo(x + y));
// 排序后,如果数组的第一个元素是"0",那么整个数组都是0。因为即使有非零数字,它们也会根据排序规则被放在"0"的后面。在这种情况下,直接返回"0"。
if (strs[0].equals("0"))
return "0";
StringBuilder res = new StringBuilder();
for (String s : strs)
res.append(s);
return res.toString();
}
}
- 时间复杂度:O(n log n * log m),其中 n 是给定序列的长度,m 是 32 位整数的最大值,每个数转化为字符串后的长度是 O(log m) 的数量级。 排序中的比较函数的时间复杂度为 O(log m)(因为要比较log m个字符),共需要进行 O(nlog n) 次比较。最后我们需要对字符串序列进行拼接,时间复杂度为 O(nlog m), 因此总的时间复杂度为 O(n log n * log m + n log m),在渐进意义上小于 O(n log n * log m)。
- 空间复杂度:排序需要 O(log n) 的栈空间。
手写快速排序,巧定排序规则
步骤:
- 把整数数组 nums 转换成字符串数组strs,因为后续需要进行字符串的拼接和比较操作。
- 采用快速排序算法对字符串数组strs进行排序。
- 选择数组的第一个元素作为基准元素。
- 双指针遍历字符串数组,将字典序小于等于基准元素的字符串放在基准元素的左边,字典序大于基准元素的字符串放在基准元素的右边。
- 一次遍历可以确定 pivot 的位置,然后递归地对左右两个子数组进行快速排序。
- 排完序后,要检查第一个元素是否为"0",如果是,则整个数组都是0,直接返回"0"。否则把字符数组拼接成字符串并返回。
细节:
- 判空
- 快速排序时注意左右指针的先后顺序
- 结果数组可能为 0,这时候直接返回一个 0,不要拼接多个 0.
class Solution {
// 交换数组中的两个元素
private void swap(String[] strs, int i, int j) {
String temp = strs[i];
strs[i] = strs[j];
strs[j] = temp;
}
void quickSort(String[] strs, int l, int r) {
if (l >= r) return;
int i = l, j = r;
// 暂存基准元素,这里选择第一个元素作为基准元素。
String pivot = strs[l];
// 当左右指针未相遇时,进行分区操作。循环结束后 pivot 左边都是字典序比 pivot 小或等的元素,右边都是字典序比 pivot 大的元素。
while (i < j) {
// 一定要让 j 在 i 前面,这样结束时,j--,即 i = j 指向最后一个字典序小于等于基准值的元素,
// 这样再和基准值交换就不会错。本质是我们取了最左侧的元素作为基准值。
// 从数组的右端开始向左扫描,找到第一个使得 strs[j] + pivot 的字典序小于等于 pivot + strs[j] 的元素。
while ((strs[j] + pivot).compareTo(pivot + strs[j]) > 0 && i < j) j--;
// 从数组的左端开始向右扫描,找到第一个使得 strs[i] + pivot 的字典序大于 pivot + strs[i] 的元素。
while ((strs[i] + pivot).compareTo(pivot + strs[i]) <= 0 && i < j) i++;
swap(strs, i, j);
}
// 分区结束后,i 和 j 相遇,此时将基准元素与相遇点的元素交换,确保基准元素归位至最终排序后的正确位置。
swap(strs, l, i);
// 对基准元素左侧和右侧的子数组递归执行快速排序。
quickSort(strs, l, i - 1);
quickSort(strs, i + 1, r);
}
public String largestNumber(int[] nums) {
// 把nums转为字符串数组
String[] strs = new String[nums.length];
for (int i = 0; i < nums.length; i++)
strs[i] = String.valueOf(nums[i]);
quickSort(strs, 0, strs.length - 1);
StringBuilder res = new StringBuilder();
// 注意这里判断的是最后一个元素,这是由快速排序中的规则决定的
if (strs[strs.length - 1].equals("0"))
return "0";
// 注意,这里是从后往前遍历,这是由快速排序中的规则决定的
for (int i = strs.length - 1; i >= 0; i--)
res.append(strs[i]);
return res.toString();
}
}
复杂度同方法一。