← back

Sorting Techniques

Custom Comparator Sort

Define a comparator that determines the final order. For largest number, compare str(a)+str(b) vs str(b)+str(a). Reduces a complex ordering problem to a pairwise comparison.

Python's built-in sorted() and .sort() methods order elements in ascending order by default: integers numerically, strings lexicographically. That works perfectly for most problems, but a surprising number of interview questions require a completely different ordering. The moment you catch yourself thinking "I need to sort these, but not in the normal way," a custom comparator is probably your tool.

The classic trigger problem is Largest Number (LC 179): given a list of integers, arrange them to form the largest possible number. With [3, 30, 34, 5, 9], standard numeric sort gives [3, 5, 9, 30, 34], which concatenates to "3593034", nowhere near optimal. The correct answer is "9534330".

The Key Insight: Compare Concatenations

The ordering rule for largest number is: number a should come before number b if the string a+b is lexicographically greater than b+a. For example, comparing "9" and "34": "934" > "349", so 9 goes first. Comparing "3" and "30": "330" > "303", so 3 goes before 30.

This comparison relation is transitive, meaning it defines a valid total order and can be used as a sort key. The tricky part is that Python 3 removed the old-style cmp argument from sorted(). Instead, you convert a two-argument comparator into a key function using functools.cmp_to_key.

A comparator function takes two arguments a and b and must return:

  • a negative number if a should come first
  • a positive number if b should come first
  • zero if they are equal

Walking Through the Example

Starting with [3, 30, 34, 5, 9], convert everything to strings: ["3", "30", "34", "5", "9"].

The comparator checks a+b versus b+a. A few representative comparisons:

  • "9" vs "5": "95" > "59", so 9 comes first.
  • "5" vs "34": "534" > "345", so 5 comes first.
  • "3" vs "30": "330" > "303", so 3 comes first.
  • "30" vs "34": "3034" < "3430", so 34 comes first.

After sorting: ["9", "5", "34", "3", "30"]. Concatenated: "9534330". One edge case: if every number is zero, the result is all zeros, so return "0" rather than "000...".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from functools import cmp_to_key

def largest_number(nums: list[int]) -> str:
    strs = list(map(str, nums))

    def compare(a, b):
        if a + b > b + a:
            return -1   # a should come first
        elif a + b < b + a:
            return 1    # b should come first
        return 0

    strs.sort(key=cmp_to_key(compare))
    result = "".join(strs)
    return "0" if result[0] == "0" else result

The lambda version you'll often see in competitive code condenses this to a one-liner: key=cmp_to_key(lambda a, b: (1 if a+b < b+a else -1 if a+b > b+a else 0)). Both are correct; the explicit function is easier to read under pressure.

Multi-Key Sorting with Tuples

When you need to sort by multiple criteria, say by priority descending and then by name ascending, Python's tuple comparison gives you multi-key sorting for free. Because tuples are compared element by element, returning a tuple from a key function lets you express mixed ascending/descending orderings cleanly.

1
2
3
4
5
tasks = [("task_a", 2), ("task_b", 1), ("task_c", 2)]

# Sort by priority descending, then by name ascending
tasks.sort(key=lambda t: (-t[1], t[0]))
# Result: [("task_a", 2), ("task_c", 2), ("task_b", 1)]

Negating a numeric field flips its sort direction without needing a custom comparator at all. This tuple-key approach is almost always preferable to cmp_to_key when a simple key expression can express the ordering.

Interval Sorting for Greedy Problems

Interval scheduling problems often live or die on how you sort before applying the greedy pass. The two most common orderings are:

Sort by end time (earliest deadline first) is used in the classic activity selection / non-overlapping intervals problem. Sorting intervals by their end time lets the greedy algorithm always pick the interval that leaves the most room for future choices.

Sort by start time is used when merging overlapping intervals. After sorting by start, a single left-to-right sweep can merge intervals because any overlap must involve the previous interval's end and the current interval's start.

1
2
3
4
5
# Sort intervals by end time for greedy scheduling
intervals.sort(key=lambda x: x[1])

# Sort intervals by start time for merging
intervals.sort(key=lambda x: x[0])

These are so common that recognizing which ordering a greedy interval problem needs is itself a key skill.

Queue Reconstruction by Height

LeetCode 406. Queue Reconstruction by Height is a beautiful example of how sorting order determines algorithm correctness. You are given a list of people described by [height, k], where k is the number of people in front who are taller or equal in height. Reconstruct the queue.

The insight: sort people by height descending, then by k ascending. Then insert each person into the result list at index k. Because taller people are placed first, when you insert a shorter person at position k, all previously placed (taller) people are already in the list and will correctly be "in front" of them. Shorter people inserted later never affect the k-value of taller people already placed.

1
2
3
4
5
6
7
def reconstructQueue(people):
    # Sort: tallest first; among same height, smaller k first
    people.sort(key=lambda p: (-p[0], p[1]))
    queue = []
    for person in people:
        queue.insert(person[1], person)
    return queue

Walkthrough: [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

After sorting by (-height, k): [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]

Insert step by step:

  • Insert [7,0] at index 0: [[7,0]]
  • Insert [7,1] at index 1: [[7,0], [7,1]]
  • Insert [6,1] at index 1: [[7,0], [6,1], [7,1]]
  • Insert [5,0] at index 0: [[5,0], [7,0], [6,1], [7,1]]
  • Insert [5,2] at index 2: [[5,0], [7,0], [5,2], [6,1], [7,1]]
  • Insert [4,4] at index 4: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

Verify the final queue [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] by counting people in front who are taller or equal in height. [5,0]: nobody in front. [7,0]: nobody in front. [5,2]: [5,0] (equal) and [7,0] (taller), that's 2. [6,1]: [7,0] (taller), that's 1. [4,4]: [5,0], [7,0], [5,2], [6,1] are all at least 4, that's 4. [7,1]: [7,0] (equal), that's 1. All k-values check out.

The sorting order is the entire algorithm. Without the right comparator, the greedy insertion strategy breaks down completely.

Complexity

Both sorted() and .sort() use Timsort, so the time complexity is O(nlog⁡n)O(n \log n) regardless of how the comparator is defined. Each comparison call is O(1)O(1) for numeric keys and O(k)O(k) for string concatenation comparisons, where k is the average number of digits. Space complexity is O(n)O(n) for the auxiliary array produced by sorted(), or O(log⁡n)O(\log n) for the in-place .sort() (stack space for Timsort's merge passes).

When the comparison itself is expensive, for instance comparing long strings, the total cost becomes O(nlog⁡n⋅k)O(n \log n · k). In most interview problems, k is small enough that this doesn't matter, but it's worth noting if strings can be arbitrarily long.

Recognizing this pattern

You're probably looking at custom comparator sort when:

  • The natural sort order does not produce the desired result, so you need a non-obvious key combining two values.
  • The greedy strategy depends entirely on the ordering, like queue reconstruction or schedule assembly.
  • The comparison rule is asymmetric or non-numeric, such as concatenation order between strings.
  • Phrasing mentions "reconstruct the queue", "largest number you can form", or "order by custom rule".

Common templates:

  • Pairwise concatenation rule: sort by a + b vs b + a comparison. Example: Largest Number.
  • Tall-first insertion: sort by (-height, k) then insert at index k. Example: Queue Reconstruction by Height.
  • Sort by deadline or end time: greedy scheduling by interval finish or due date. Example: Course Schedule III.
  • Stable secondary key: sort by primary key, break ties with a meaningful secondary like input order. Example: Sort the People.

Practice Problems (95)

⊣ 179 Largest Number
⊣ 324 Wiggle Sort II
⊣ 406 Queue Reconstruction by Height
⊣ 506 Relative Ranks
⊣ 539 Minimum Time Difference
⊣ 561 Array Partition
⊣ 628 Maximum Product of Three Numbers
⊣ 791 Custom Sort String
⊣ 870 Advantage Shuffle
⊣ 899 Orderly Queue
⊣ 937 Reorder Data in Log Files
⊣ 954 Array of Doubled Pairs
⊣ 1005 Maximize Sum Of Array After K Negations
⊣ 1029 Two City Scheduling
⊣ 1051 Height Checker
⊣ 1090 Largest Values From Labels
⊣ 1122 Relative Sort Array
⊣ 1200 Minimum Absolute Difference
⊣ 1329 Sort the Matrix Diagonally
⊣ 1331 Rank Transform of an Array
⊣ 1333 Filter Restaurants by Vegan-Friendly, Price and Distance
⊣ 1356 Sort Integers by The Number of 1 Bits
⊣ 1366 Rank Teams by Votes
⊣ 1403 Minimum Subsequence in Non-Increasing Order
⊣ 1433 Check If a String Can Break Another String
⊣ 1451 Rearrange Words in a Sentence
⊣ 1465 Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts
⊣ 1471 The k Strongest Values in an Array
⊣ 1502 Can Make Arithmetic Progression From Sequence
⊣ 1509 Minimum Difference Between Largest and Smallest Value in Three Moves
⊣ 1536 Minimum Swaps to Arrange a Binary Grid
⊣ 1561 Maximum Number of Coins You Can Get
⊣ 1604 Alert Using Same Key-Card Three or More Times in a One Hour Period
⊣ 1605 Find Valid Matrix Given Row and Column Sums
⊣ 1619 Mean of Array After Removing Some Elements
⊣ 1630 Arithmetic Subarrays
⊣ 1636 Sort Array by Increasing Frequency
⊣ 1637 Widest Vertical Area Between Two Points Containing No Points
⊣ 1657 Determine if Two Strings Are Close
⊣ 1665 Minimum Initial Energy to Finish Tasks
⊣ 1686 Stone Game VI
⊣ 1710 Maximum Units on a Truck
⊣ 1727 Largest Submatrix With Rearrangements
⊣ 1840 Maximum Building Height
⊣ 1846 Maximum Element After Decreasing and Rearranging
⊣ 1847 Closest Room
⊣ 1887 Reduction Operations to Make the Array Elements Equal
⊣ 1913 Maximum Product Difference Between Two Pairs
⊣ 1968 Array With Elements Not Equal to Average of Neighbors
⊣ 1982 Find Array Given Subset Sums
⊣ 1996 The Number of Weak Characters in the Game
⊣ 2033 Minimum Operations to Make a Uni-Value Grid
⊣ 2054 Two Best Non-Overlapping Events
⊣ 2126 Destroying Asteroids
⊣ 2136 Earliest Possible Day of Full Bloom
⊣ 2144 Minimum Cost of Buying Candies With Discount
⊣ 2164 Sort Even and Odd Indices Independently
⊣ 2165 Smallest Value of the Rearranged Number
⊣ 2171 Removing Minimum Number of Magic Beans
⊣ 2191 Sort the Jumbled Numbers
⊣ 2195 Append K Integers With Minimal Sum
⊣ 2197 Replace Non-Coprime Numbers in Array
⊣ 2231 Largest Number After Digit Swaps by Parity
⊣ 2274 Maximum Consecutive Floors Without Special Floors
⊣ 2279 Maximum Bags With Full Capacity of Rocks
⊣ 2294 Partition Array Such That Maximum Difference Is K
⊣ 2332 The Latest Time to Catch a Bus
⊣ 2418 Sort the People
⊣ 2449 Minimum Number of Operations to Make Arrays Similar
⊣ 2500 Delete Greatest Value in Each Row
⊣ 2545 Sort the Students by Their Kth Score
⊣ 2551 Put Marbles in Bags
⊣ 2567 Minimum Score by Changing Two Elements
⊣ 2578 Split With Minimum Sum
⊣ 2587 Rearrange Array to Maximize Prefix Score
⊣ 2785 Sort Vowels in a String
⊣ 2931 Maximum Spending After Buying Items
⊣ 2966 Divide Array Into Arrays With Max Difference
⊣ 3025 Find the Number of Ways to Place People I
⊣ 3027 Find the Number of Ways to Place People II
⊣ 3273 Minimum Amount of Damage Dealt to Bob
⊣ 3301 Maximize the Total Height of Unique Towers
⊣ 3397 Maximum Number of Distinct Elements After Operations
⊣ 3424 Minimum Cost to Make Arrays Identical
⊣ 3446 Sort Matrix by Diagonals
⊣ 3457 Eat Pizzas!
⊣ 3551 Minimum Swaps to Sort by Digit Sum
⊣ 3727 Maximum Alternating Sum of Squares
⊣ 3732 Maximum Product of Three Elements After One Replacement
⊣ 3745 Maximize Expression of Three Elements
⊣ 3759 Count Elements With at Least K Greater Values
⊣ 3769 Sort Integers by Binary Reflection
⊣ 3774 Absolute Difference Between Maximum and Minimum K Elements
⊣ 3776 Minimum Moves to Balance Circular Array
⊣ 3780 Maximum Sum of Three Numbers Divisible by Three