179. Largest Number
Problem Description
You are given an array of non-negative integers nums
. Your task is to arrange these numbers in such a way that when concatenated together, they form the largest possible number.
For example, if you have nums = [3, 30, 34, 5, 9]
, you need to arrange them to form the largest number possible. The arrangement [9, 5, 34, 3, 30]
gives us "9534330", which is the largest number that can be formed.
The key challenge is determining the correct order. Simply sorting numbers in descending order won't work. For instance, with numbers 3
and 30
, placing 3
before 30
gives "330", which is larger than "303" (obtained by placing 30
before 3
).
The solution uses a custom comparison function. For any two numbers a
and b
, we compare the concatenated results a + b
and b + a
. If a + b
is larger, then a
should come before b
in the final arrangement.
The algorithm:
- Convert all integers to strings for easy concatenation
- Sort the strings using a custom comparator that checks which concatenation order produces a larger result
- Join all sorted strings together
- Handle the edge case where all numbers are zeros (return "0" instead of "000...")
The time complexity is O(n log n)
due to sorting, where n
is the number of elements in the array.
Intuition
The natural first thought might be to sort the numbers in descending order - put the largest numbers first. But this quickly falls apart with examples like [3, 30]
. If we sort by numeric value, we'd get [30, 3]
โ "303", but [3, 30]
โ "330" is actually larger.
The key insight comes from asking: when should one number come before another in our final arrangement?
Consider two numbers a
and b
. We have exactly two ways to arrange them:
- Put
a
first: results in concatenationab
- Put
b
first: results in concatenationba
We want whichever arrangement gives us the larger number. So we should put a
before b
if and only if ab > ba
.
This comparison rule is transitive, meaning if a
should come before b
, and b
should come before c
, then a
should come before c
. This property allows us to use sorting with our custom comparison.
For example, with [3, 30, 34, 5, 9]
:
- Compare
3
and30
: "330" > "303", so3
comes before30
- Compare
34
and3
: "343" < "334", so3
comes before34
- Compare
9
and5
: "95" > "59", so9
comes before5
By applying this comparison rule across all pairs through sorting, we get the optimal arrangement: [9, 5, 34, 3, 30]
.
The string conversion makes the concatenation comparison straightforward - we can directly compare "330" with "303" as strings, since string comparison follows lexicographic order which aligns with numeric comparison for same-length number strings.
Solution Approach
The implementation follows a custom sorting strategy using Python's built-in sorting capabilities with a custom comparator.
Step 1: Convert numbers to strings
nums = [str(v) for v in nums]
We convert all integers to strings first. This allows us to easily concatenate and compare different arrangements. For example, 3
becomes "3"
and 30
becomes "30"
.
Step 2: Custom sorting with comparison function
nums.sort(key=cmp_to_key(lambda a, b: 1 if a + b < b + a else -1))
This is the core of the solution. We use cmp_to_key
from Python's functools
module to convert a comparison function into a key function for sorting.
The lambda function lambda a, b: 1 if a + b < b + a else -1
works as follows:
- Takes two string numbers
a
andb
- Concatenates them both ways:
a + b
andb + a
- Returns
1
ifa + b < b + a
(meaningb
should come beforea
) - Returns
-1
ifa + b >= b + a
(meaninga
should come beforeb
)
For example, comparing "3"
and "30"
:
"3" + "30" = "330"
"30" + "3" = "303"
- Since
"330" > "303"
, the function returns-1
, placing"3"
before"30"
Step 3: Handle edge case and return result
return "0" if nums[0] == "0" else "".join(nums)
After sorting, we check if the first element is "0"
. If it is, this means all elements must be "0"
(since "0"
would only be first if no other digit could form a larger number). In this case, we return "0"
instead of "000..."
.
Otherwise, we join all sorted strings together to form the final result.
The algorithm efficiently handles all cases through this comparison-based sorting, ensuring the largest possible number is formed with O(n log n)
time complexity for the sort operation and O(n)
space complexity for storing the string representations.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with nums = [3, 30, 34]
to see how the solution works step by step.
Step 1: Convert to strings
- Input:
[3, 30, 34]
- After conversion:
["3", "30", "34"]
Step 2: Apply custom sorting
The sorting algorithm will compare pairs of strings using our custom rule: for strings a
and b
, place a
before b
if a + b > b + a
.
Let's trace through the comparisons:
-
Compare
"3"
and"30"
:"3" + "30" = "330"
"30" + "3" = "303"
- Since
"330" > "303"
, keep"3"
before"30"
-
Compare
"3"
and"34"
:"3" + "34" = "334"
"34" + "3" = "343"
- Since
"334" < "343"
,"34"
should come before"3"
-
Compare
"30"
and"34"
:"30" + "34" = "3034"
"34" + "30" = "3430"
- Since
"3034" < "3430"
,"34"
should come before"30"
After sorting with these comparisons, we get: ["34", "3", "30"]
Step 3: Join and handle edge cases
- First element is
"34"
, not"0"
, so no edge case handling needed - Join the strings:
"34" + "3" + "30" = "34330"
Result: "34330"
We can verify this is correct by checking other arrangements:
[3, 30, 34]
โ "33034"[3, 34, 30]
โ "33430"[30, 3, 34]
โ "30334"[30, 34, 3]
โ "30343"[34, 30, 3]
โ "34303"[34, 3, 30]
โ "34330" โ (largest)
The custom comparison ensures that each number is positioned optimally relative to all others, producing the largest possible concatenated result.
Solution Implementation
1from typing import List
2from functools import cmp_to_key
3
4class Solution:
5 def largestNumber(self, nums: List[int]) -> str:
6 # Convert all integers to strings for comparison
7 nums_str = [str(num) for num in nums]
8
9 # Custom comparator: if concatenating a+b yields a smaller number than b+a,
10 # then a should come after b in the sorted order (return 1)
11 # This ensures larger concatenations come first
12 def compare(a: str, b: str) -> int:
13 if a + b < b + a:
14 return 1 # a should come after b
15 else:
16 return -1 # a should come before b
17
18 # Sort the string numbers using the custom comparator
19 nums_str.sort(key=cmp_to_key(compare))
20
21 # Edge case: if the largest number is "0", all numbers must be zeros
22 # Return "0" instead of "0000..."
23 if nums_str[0] == "0":
24 return "0"
25
26 # Join all sorted strings to form the largest number
27 return "".join(nums_str)
28
1class Solution {
2 public String largestNumber(int[] nums) {
3 // Convert all integers to strings for custom comparison
4 List<String> numStrings = new ArrayList<>();
5 for (int num : nums) {
6 numStrings.add(String.valueOf(num));
7 }
8
9 // Sort strings based on which concatenation produces a larger number
10 // For example: comparing "3" and "30" -> "330" vs "303" -> "330" is larger
11 numStrings.sort((a, b) -> (b + a).compareTo(a + b));
12
13 // Handle edge case: if the largest number is "0", all numbers must be 0
14 if (numStrings.get(0).equals("0")) {
15 return "0";
16 }
17
18 // Concatenate all sorted strings to form the largest number
19 return String.join("", numStrings);
20 }
21}
22
1class Solution {
2public:
3 string largestNumber(vector<int>& nums) {
4 // Convert all integers to strings for custom comparison
5 vector<string> numStrings;
6 for (int num : nums) {
7 numStrings.push_back(to_string(num));
8 }
9
10 // Sort strings based on which concatenation produces a larger number
11 // For example: "3" and "30" -> "330" > "303", so "3" comes before "30"
12 sort(numStrings.begin(), numStrings.end(), [](const string& a, const string& b) {
13 return a + b > b + a;
14 });
15
16 // Handle edge case: if the largest number is "0", all numbers must be 0
17 if (numStrings[0] == "0") {
18 return "0";
19 }
20
21 // Concatenate all sorted strings to form the largest number
22 string result;
23 for (const string& numStr : numStrings) {
24 result += numStr;
25 }
26
27 return result;
28 }
29};
30
1/**
2 * Given a list of non-negative integers, arrange them to form the largest number.
3 * @param nums - Array of non-negative integers to arrange
4 * @returns The largest number as a string
5 */
6function largestNumber(nums: number[]): string {
7 // Sort numbers based on which combination produces a larger value
8 // Compare by concatenating in different orders (a+b vs b+a)
9 nums.sort((a: number, b: number) => {
10 // Convert numbers to strings for concatenation
11 const combinationAB: string = String(a) + String(b);
12 const combinationBA: string = String(b) + String(a);
13
14 // Sort in descending order by comparing concatenated values
15 // Convert back to numbers for comparison
16 return Number(combinationBA) - Number(combinationAB);
17 });
18
19 // Handle edge case: if the largest number is 0, all numbers are 0
20 // Return "0" instead of "000...0"
21 if (nums[0] === 0) {
22 return '0';
23 }
24
25 // Join all sorted numbers into a single string
26 return nums.join('');
27}
28
Time and Space Complexity
Time Complexity: O(n log n * m)
- Converting all integers to strings takes
O(n)
time wheren
is the number of elements - The sorting operation dominates the time complexity. For sorting
n
elements:- The sort uses
O(n log n)
comparisons - Each comparison involves string concatenation (
a + b
andb + a
) where the average length of the strings ism
- String concatenation takes
O(m)
time - Therefore, total sorting time is
O(n log n * m)
- The sort uses
- Joining the sorted strings takes
O(n * m)
time - Overall time complexity:
O(n log n * m)
wheren
is the number of elements andm
is the average length of the string representation of the numbers
Space Complexity: O(n * m)
- Converting integers to strings creates a new list of
n
strings, each with average lengthm
, requiringO(n * m)
space - The sorting algorithm (typically Timsort in Python) requires
O(n)
auxiliary space - The final joined string requires
O(n * m)
space - Overall space complexity:
O(n * m)
wheren
is the number of elements andm
is the average length of the string representation of the numbers
Common Pitfalls
1. Forgetting the All-Zeros Edge Case
The Pitfall:
When all numbers in the array are zeros (e.g., [0, 0, 0]
), simply joining the sorted strings would produce "000"
instead of the expected "0"
. This is incorrect because leading zeros in a number representation should be eliminated, and multiple zeros should be represented as a single "0"
.
Why it happens:
After sorting, if all elements are "0"
, they remain as individual "0"
strings. Without checking for this case, "".join(["0", "0", "0"])
produces "000"
.
Solution:
Always check if the first element after sorting is "0"
. If it is, return "0"
immediately:
if nums_str[0] == "0": return "0"
This works because if "0"
is the first element after our custom sorting, it means there's no other digit that could form a larger number when placed first.
2. Using Standard Numerical Comparison Instead of String Concatenation
The Pitfall: Attempting to compare numbers directly or using standard lexicographic string comparison without considering concatenation:
# Wrong approach 1: Direct numerical comparison nums.sort(reverse=True) # [9, 5, 34, 30, 3] gives "9534303" instead of "9534330" # Wrong approach 2: Simple string comparison nums_str.sort(reverse=True) # ["9", "5", "34", "30", "3"] gives "9534303"
Why it fails:
The numbers 3
and 30
demonstrate this issue clearly. Numerically, 30 > 3
, but when forming the largest number, "3"
should come before "30"
because "330" > "303"
.
Solution: Always compare the concatenated results:
def compare(a: str, b: str) -> int:
if a + b < b + a: # Compare "ab" vs "ba"
return 1
else:
return -1
3. Incorrect Comparator Return Values
The Pitfall: Reversing the return values in the comparison function, which would sort in ascending order instead of descending:
# Wrong: This would place smaller concatenations first
def compare(a: str, b: str) -> int:
if a + b < b + a:
return -1 # Incorrect: would place a before b when a+b is smaller
else:
return 1
Why it happens:
The comparison function's return value convention can be confusing. Returning 1
means "a comes after b" and returning -1
means "a comes before b".
Solution:
Remember that we want larger concatenations first, so when a + b < b + a
, we should return 1
to place a
after b
:
def compare(a: str, b: str) -> int:
if a + b < b + a:
return 1 # a should come AFTER b (b forms larger number)
else:
return -1 # a should come BEFORE b (a forms larger number)
4. Not Converting Numbers to Strings Before Comparison
The Pitfall: Attempting to concatenate integers directly:
# This will cause a TypeError
def compare(a: int, b: int) -> int:
if a + b < b + a: # This performs addition, not concatenation!
return 1
Why it fails:
Integer addition (3 + 30 = 33
) is completely different from string concatenation ("3" + "30" = "330"
).
Solution: Always convert to strings first:
nums_str = [str(num) for num in nums]
Then perform all comparisons on these string representations.
Which technique can we use to find the middle of a linked list?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!