LeetCode 179. 最大数
问题描述
给定一组非负整数 nums
,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。注意:结果可能非常大,需返回字符串形式。
示例:
输入: [10,2]
输出: "210"
输入: [3,30,34,5,9]
输出: "9534330"
算法思路
核心:自定义排序
- 问题本质:比较两个数字不同拼接顺序的结果大小
- 关键策略:定义比较规则
(a, b) -> (b + a).compareTo(a + b)
- 若
b+a
>a+b
,则b
应排在a
前
- 若
- 处理前导零:若排序后首位为 ‘0’,则直接返回 “0”
代码实现
class Solution {
public String largestNumber(int[] nums) {
// 将整数数组转换为字符串数组
String[] numStrs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
numStrs[i] = String.valueOf(nums[i]);
}
// 自定义排序:比较拼接后的字典序
Arrays.sort(numStrs, (a, b) -> {
String order1 = b + a; // b在前a在后的组合
String order2 = a + b; // a在前b在后的组合
return order1.compareTo(order2); // 降序排列
});
// 处理全零情况
if (numStrs[0].equals("0")) {
return "0";
}
// 拼接结果字符串
StringBuilder sb = new StringBuilder();
for (String s : numStrs) {
sb.append(s);
}
return sb.toString();
}
}
算法分析
- 时间复杂度:O(n log n)
排序主导时间复杂度(字符串比较 O(k),k 为字符串长度) - 空间复杂度:O(n)
存储字符串数组
算法过程
输入 [3,30,34,5,9]
:
- 转换为字符串:
["3","30","34","5","9"]
- 自定义排序:
- 比较 “9” 和 “5”:
"95"
vs"59"
→"95">"59"
→ “9” 排在 “5” 前 - 比较 “5” 和 “34”:
"534"
vs"345"
→"534">"345"
→ “5” 排在 “34” 前 - 比较 “34” 和 “3”:
"343"
vs"334"
→"343">"334"
→ “34” 排在 “3” 前 - 比较 “3” 和 “30”:
"330"
vs"303"
→"330">"303"
→ “3” 排在 “30” 前
- 比较 “9” 和 “5”:
- 排序结果:
["9","5","34","3","30"]
- 拼接字符串:
"9534330"
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例1:标准示例
int[] nums1 = {10,2};
System.out.println("Test 1: " + solution.largestNumber(nums1)); // "210"
// 测试用例2:含前缀相同的数字
int[] nums2 = {3,30,34,5,9};
System.out.println("Test 2: " + solution.largestNumber(nums2)); // "9534330"
// 测试用例3:全零数组
int[] nums3 = {0,0,0,0};
System.out.println("Test 3: " + solution.largestNumber(nums3)); // "0"
// 测试用例4:单个元素
int[] nums4 = {1};
System.out.println("Test 4: " + solution.largestNumber(nums4)); // "1"
// 测试用例5:大数组合
int[] nums5 = {999999998,999999997,999999999};
System.out.println("Test 5: " + solution.largestNumber(nums5)); // "999999999999999998999999997"
// 测试用例6:含0的非全零数组
int[] nums6 = {10,0,2};
System.out.println("Test 6: " + solution.largestNumber(nums6)); // "2100"
// 测试用例7:易错案例
int[] nums7 = {34323,3432}; // 34323+3432=343233432 < 3432+34323=343234323
System.out.println("Test 7: " + solution.largestNumber(nums7)); // "343234323"
}
关键点
-
比较规则设计:
- 核心:
(b+a).compareTo(a+b)
实现降序排列 - 证明:若
a+b > b+a
则a
应排在b
前
- 核心:
-
前导零处理:
- 仅需检查排序后首位(若首位为 ‘0’ 则必全零)
- 避免输出 “00…0” 非法格式
-
大数处理:
- 直接操作字符串避免数值溢出
- 字符串比较按字典序(与数值序一致)
常见问题
1. 为什么不用数值比较?
拼接后数值可能超 long
范围(如 [999999998,999999997,999999999]
)。
2. 比较规则如何保证正确性?
传递性证明:若 a≻b
(即 a+b>b+a
)且 b≻c
,则 a≻c
。
反例:a=3,b=32,c=321
3+32="332">"323"=32+3
→a≻b
32+321="32321">"32132"=321+32
→b≻c
3+321="3321">"3213"=321+3
→a≻c
本例满足传递性
3. 时间复杂度是否过高?
- 字符串比较 O(k)(k 为数字字符串平均长度)
- 总复杂度 O(nk log n),k ≤ 10,实际效率接近 O(n log n)
4. 如何验证极端案例?
测试用例7展示:34323
与 3432
比较
34323+3432="343233432"
3432+34323="343234323"
- 字典序比较:
"343234323" > "343233432"
3432
应排在34323
前