Rank Teams by Votes: Positional Sorting Pattern
LeetCode Rank Teams by Votes (problem 1366) asks you to aggregate a set of ranked ballots into a single consensus ranking. Each voter submits a string where characters represent teams in their preferred order. You need to combine all the votes and produce the final ranking.
This is a clean application of two patterns you will see again and again: counting into a multi-dimensional structure, and writing a custom comparator for sorting.
Why this problem matters
Positional vote counting shows up in more places than you might expect. Any time you need to rank items based on multi-criteria preferences, the same pattern applies. Think of it as a generalized "sort by column A, then column B, then column C" problem, where the columns come from counting.
The problem also tests your ability to design a custom sort key. Many interview problems give you a collection of items and ask you to sort them by some non-obvious criterion. The technique you learn here, building a composite sort key from counted data, transfers directly to those problems.
Finally, this is a great exercise in choosing the right data structure. You need a hash map to count, and you need a list (or tuple) to serve as a sortable key. Combining these two building blocks into a clean solution is the whole challenge.
The key insight
Each vote is a permutation of the teams. The position of a team in a vote tells you how highly that voter ranked it. To find the overall ranking, you:
- Count how many times each team appears at each position across all votes
- Sort teams by their 1st-place count (descending)
- Break ties using 2nd-place count, then 3rd-place count, and so on
- If all position counts are identical, break ties alphabetically by team name
That is the entire algorithm. The counting step is O(n * k) where n is the number of votes and k is the number of teams. The sorting step uses a composite key built from the counts.
The solution
def rank_teams(votes: list[str]) -> str:
teams = votes[0]
k = len(teams)
count = {team: [0] * k for team in teams}
for vote in votes:
for pos, team in enumerate(vote):
count[team][pos] += 1
ranked = sorted(teams, key=lambda t: ([-c for c in count[t]], t))
return "".join(ranked)
The solution starts by initializing a hash map where each team maps to a list of k zeros, one slot per position. Then it processes every vote: for each position in each vote, it increments the corresponding counter for that team.
The sorting step is where the magic happens. The key function returns a tuple of negated counts followed by the team name. Negating the counts makes Python's default ascending sort behave like descending sort for the counts. The team name at the end handles alphabetical tiebreaking.
For example, with the tally A=[5,0,0], B=[0,2,3], C=[0,3,2], the sort keys become:
- A: ([-5, 0, 0], "A")
- B: ([0, -2, -3], "B")
- C: ([0, -3, -2], "C")
Since -5 is the smallest, A sorts first. Between B and C, both have 0 for the first element, but C has -3 vs B's -2 for the second element, so C sorts before B. Final result: "ACB".
Negating counts to turn a descending sort into an ascending sort is a common Python trick. It avoids having to write a custom comparator with functools.cmp_to_key, keeping the code shorter and more readable.
Visual walkthrough
Step 1: Process the first vote "ABC"
A gets +1 in the 1st column, B gets +1 in the 2nd column, C gets +1 in the 3rd column.
Step 2: Process the second vote "ACB"
A gets another 1st-place vote. C gets a 2nd-place vote. B gets a 3rd-place vote.
Step 3: After all 5 votes are counted
Full tally: A has 5 first-place votes, B has 2 second-place and 3 third-place, C has 3 second-place and 2 third-place.
Step 4: Sort teams by their vote counts
A has 5 first-place votes. Clear winner for rank 1.
B vs C tied at 0 first-place votes. Check 2nd-place: C has 3, B has 2. C wins the tiebreaker.
Compare 1st-place counts first. If tied, compare 2nd-place. If still tied, compare 3rd-place, and so on.
Step 5: Final ranking is "ACB"
A is first (5 top votes), C is second (3 second-place votes beats B's 2), B is third.
Notice how the tiebreaker between B and C is resolved entirely by the 2nd-place column. B and C both have zero 1st-place votes, but C has three 2nd-place votes compared to B's two. That single column difference determines the final order.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Count + custom sort | O(n * k + k * k * log k) | O(k * k) |
Where n = number of votes and k = number of teams.
Time: You iterate through all n votes, and for each vote you process k team positions, giving O(n * k) for the counting phase. Sorting k teams requires O(k * log k) comparisons, and each comparison examines up to k position counts, giving O(k * k * log k) for the sort. The total is O(n * k + k * k * log k).
Space: The count map stores k teams, each with a list of k integers, giving O(k * k). The sorted output is O(k). Since k is at most 26 (uppercase English letters), the space is effectively constant for this problem's constraints.
The building blocks
1. Multi-dimensional counting
count = {team: [0] * k for team in teams}
for vote in votes:
for pos, team in enumerate(vote):
count[team][pos] += 1
This pattern extends the basic frequency counter (which maps items to single integers) into a structure where each item maps to a list of counts. You use this whenever an item can appear in multiple categories or positions and you need to track all of them. The same idea appears in problems like vote tabulation, histogram construction, and multi-attribute classification.
2. Composite sort keys
sorted(teams, key=lambda t: ([-c for c in count[t]], t))
Building a tuple as a sort key lets you define multi-level sorting without writing a custom comparator. Python compares tuples element by element, so the first element is the primary sort criterion, the second is the tiebreaker, and so on. Negating numeric values converts a descending requirement into ascending order. This technique shows up whenever you need to sort by multiple criteria with mixed ascending/descending directions.
Edge cases
- Single voter. If there is only one vote, the ranking is just that vote itself. The counting and sorting still produce the correct result, but you can also short-circuit.
- All voters agree. Every vote is the same string. Each team has all its votes concentrated in one position. The sort produces that unanimous ranking.
- Two teams tied across all positions. If teams X and Y have identical counts at every position, the alphabetical tiebreaker kicks in. Team X comes before Y because "X" is lexicographically smaller (wait, actually Y before X, so make sure your tiebreaker sorts ascending by name).
- Only one team. The input has single-character votes like ["A", "A", "A"]. The output is "A". The algorithm handles this without special casing.
- Maximum 26 teams. Since teams are uppercase English letters, k is at most 26. This bounds the inner dimensions of both time and space.
- All votes are distinct permutations. No two voters agree on any ranking. The counting still works. The team with the most 1st-place votes leads, with tiebreakers cascading through subsequent positions.
From understanding to recall
The code for this problem is short, but the pattern it demonstrates, positional counting followed by composite-key sorting, appears in many forms. The next time you see a problem that says "rank these items by multiple criteria," you will want to reach for this exact structure: count into a multi-dimensional map, then sort with a tuple key.
The part that tends to fade from memory is the negation trick for descending sort. It feels obvious when you see it, but under interview pressure you might reach for cmp_to_key or try to reverse the sort, both of which are more error-prone. Practice writing the lambda with negated counts until it feels automatic.
One more thing to internalize: the tiebreaker by team name. It is easy to forget the alphabetical fallback and produce an answer that differs from the expected output only when two teams are perfectly tied. Edge cases like this are exactly what spaced repetition catches, because you will encounter them in practice drills long before you hit them in an interview.
Related posts
- Group Anagrams - Hash map grouping with custom keys
- Sort Characters by Frequency - Custom character sorting
- Top K Frequent Elements - Counting and ranking pattern
CodeBricks helps you move from understanding a problem to recalling the solution under pressure. Instead of re-reading explanations, you practice writing the code from memory at spaced intervals until the pattern sticks. Try it with this problem and see how quickly the positional counting and composite sort key become second nature.