Facebook Pixel

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:

  1. Convert all integers to strings for easy concatenation
  2. Sort the strings using a custom comparator that checks which concatenation order produces a larger result
  3. Join all sorted strings together
  4. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 concatenation ab
  • Put b first: results in concatenation ba

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 and 30: "330" > "303", so 3 comes before 30
  • Compare 34 and 3: "343" < "334", so 3 comes before 34
  • Compare 9 and 5: "95" > "59", so 9 comes before 5

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 and b
  • Concatenates them both ways: a + b and b + a
  • Returns 1 if a + b < b + a (meaning b should come before a)
  • Returns -1 if a + b >= b + a (meaning a should come before b)

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 Evaluator

Example 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:

  1. Compare "3" and "30":

    • "3" + "30" = "330"
    • "30" + "3" = "303"
    • Since "330" > "303", keep "3" before "30"
  2. Compare "3" and "34":

    • "3" + "34" = "334"
    • "34" + "3" = "343"
    • Since "334" < "343", "34" should come before "3"
  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 where n 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 and b + a) where the average length of the strings is m
    • String concatenation takes O(m) time
    • Therefore, total sorting time is O(n log n * m)
  • Joining the sorted strings takes O(n * m) time
  • Overall time complexity: O(n log n * m) where n is the number of elements and m 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 length m, requiring O(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) where n is the number of elements and m 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More