Facebook Pixel

1366. Rank Teams by Votes

MediumArrayHash TableStringCountingSorting
Leetcode Link

Problem Description

You have a special voting system where multiple voters rank teams. Each voter provides a complete ranking of all teams from their first choice to their last choice.

To determine the final ranking of teams, follow these rules:

  1. Primary Rule: The team with the most first-place votes ranks highest
  2. Tiebreaker Rules: If teams tie for first-place votes, compare their second-place votes. If still tied, compare third-place votes, and so on
  3. Final Tiebreaker: If teams are still tied after comparing all positions, sort them alphabetically by their team letter

Given an array votes where each string represents one voter's complete ranking (first character is their top choice, second character is their second choice, etc.), return a string showing all teams sorted according to the final ranking.

Example: If votes = ["ABC", "ACB", "ABC", "ACB", "ACB"]

  • Team A: 5 first-place votes, 0 second-place votes, 0 third-place votes
  • Team B: 0 first-place votes, 2 second-place votes, 3 third-place votes
  • Team C: 0 first-place votes, 3 second-place votes, 2 third-place votes

Final ranking: "ACB" (A has most first-place votes, C has more second-place votes than B)

The solution counts votes for each position for every team using a dictionary where each team maps to a list of vote counts [count_at_position_0, count_at_position_1, ...]. It then sorts teams by these count lists (higher counts first) and uses negative ASCII values as a tiebreaker to ensure alphabetical ordering when vote counts are identical.

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

Intuition

The key insight is that we need to compare teams based on multiple criteria with a specific priority order. Think of it like comparing sports teams by their season records - we first look at who has the most wins, then if tied, who has the most second-place finishes, and so on.

To implement this ranking system, we need to track how many votes each team receives at each position. This naturally leads us to use a counting approach where for each team, we maintain an array of counts: [votes_at_position_1, votes_at_position_2, ..., votes_at_position_n].

Why does this work? When we have these count arrays, Python's sorting can directly compare them lexicographically. For example, if Team A has counts [3, 1, 0] and Team B has counts [3, 0, 1], Python will compare element by element: first elements are equal (both 3), so it moves to second elements where 1 > 0, making Team A rank higher.

The clever part of the solution is using sorted(cnt, key=lambda c: (cnt[c], -ord(c)), reverse=True). By sorting in reverse with the count array as the primary key, teams with higher vote counts at earlier positions naturally rank higher. The secondary key -ord(c) handles the alphabetical tiebreaker - using negative ASCII values with reverse sorting ensures that when vote counts are identical, teams are ordered alphabetically (A before B before C).

This approach elegantly handles all the comparison rules in a single sort operation, avoiding complex nested comparisons or multiple sorting passes.

Learn more about Sorting patterns.

Solution Approach

The implementation uses a counting strategy combined with custom sorting to determine the final ranking of teams.

Step 1: Initialize the counting structure

We use a defaultdict with a lambda function that creates a list of zeros for each team. The list length m equals the number of teams (or length of each vote string). This gives each team an array to track votes at each position:

cnt = defaultdict(lambda: [0] * m)

Step 2: Count votes for each position

We iterate through each vote string and update the count for each team based on their position:

for vote in votes:
    for i, c in enumerate(vote):
        cnt[c][i] += 1

For example, if vote = "ABC":

  • Team 'A' gets +1 at position 0 (first place)
  • Team 'B' gets +1 at position 1 (second place)
  • Team 'C' gets +1 at position 2 (third place)

Step 3: Sort teams using custom key

The sorting uses a tuple key (cnt[c], -ord(c)) with reverse=True:

sorted(cnt, key=lambda c: (cnt[c], -ord(c)), reverse=True)

How this works:

  • cnt[c] returns the vote count array for team c, like [3, 1, 2]
  • Arrays are compared lexicographically: [3, 1, 2] > [3, 0, 5] because at index 1, we have 1 > 0
  • reverse=True ensures teams with higher counts rank first
  • -ord(c) handles alphabetical tiebreaking: since we're sorting in reverse, smaller negative values (like -65 for 'A') come before larger negative values (like -66 for 'B'), giving us alphabetical order when counts are identical

Step 4: Join and return

Finally, "".join() converts the sorted list of characters back into a string representing the final ranking.

The time complexity is O(n * m + m * m * log(m)) where n is the number of votes and m is the number of teams. The space complexity is O(mΒ²) for storing the count arrays.

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 votes = ["BCA", "CAB", "CBA"].

Step 1: Initialize counting structure We have 3 teams (A, B, C), so each team gets an array of 3 zeros:

  • Team A: [0, 0, 0]
  • Team B: [0, 0, 0]
  • Team C: [0, 0, 0]

Step 2: Count votes for each position

Processing vote "BCA":

  • B is at position 0 (1st place): B's counts become [1, 0, 0]
  • C is at position 1 (2nd place): C's counts become [0, 1, 0]
  • A is at position 2 (3rd place): A's counts become [0, 0, 1]

Processing vote "CAB":

  • C is at position 0: C's counts become [1, 1, 0]
  • A is at position 1: A's counts become [0, 1, 1]
  • B is at position 2: B's counts become [1, 0, 1]

Processing vote "CBA":

  • C is at position 0: C's counts become [2, 1, 0]
  • B is at position 1: B's counts become [1, 1, 1]
  • A is at position 2: A's counts become [0, 1, 2]

Final vote counts:

  • Team A: [0, 1, 2] (0 first-place, 1 second-place, 2 third-place votes)
  • Team B: [1, 1, 1] (1 first-place, 1 second-place, 1 third-place vote)
  • Team C: [2, 1, 0] (2 first-place, 1 second-place, 0 third-place votes)

Step 3: Sort teams

Sorting with key (cnt[c], -ord(c)) and reverse=True:

  • Team C: key = ([2, 1, 0], -67)
  • Team B: key = ([1, 1, 1], -66)
  • Team A: key = ([0, 1, 2], -65)

Comparison process:

  1. C vs B: [2, 1, 0] vs [1, 1, 1] β†’ At position 0: 2 > 1, so C ranks higher
  2. B vs A: [1, 1, 1] vs [0, 1, 2] β†’ At position 0: 1 > 0, so B ranks higher

Step 4: Final result

The sorted order is [C, B, A], which joins to give us "CBA".

This matches our expectation: C wins with the most first-place votes (2), B comes second with 1 first-place vote, and A comes last with no first-place votes.

Solution Implementation

1from typing import List
2from collections import defaultdict
3
4class Solution:
5    def rankTeams(self, votes: List[str]) -> str:
6        # Get the number of positions (length of each vote)
7        num_positions = len(votes[0])
8      
9        # Initialize a dictionary to track vote counts for each team at each position
10        # Each team will have a list of counts [1st_place_count, 2nd_place_count, ...]
11        vote_counts = defaultdict(lambda: [0] * num_positions)
12      
13        # Count votes for each team at each position
14        for vote in votes:
15            for position, team in enumerate(vote):
16                vote_counts[team][position] += 1
17      
18        # Sort teams based on:
19        # 1. Vote counts at each position (higher is better, so reverse=True)
20        # 2. Alphabetical order as tiebreaker (smaller ASCII value is better, so negative ord)
21        sorted_teams = sorted(
22            vote_counts.keys(), 
23            key=lambda team: (vote_counts[team], -ord(team)), 
24            reverse=True
25        )
26      
27        # Join the sorted teams into a string
28        return "".join(sorted_teams)
29
1class Solution {
2    public String rankTeams(String[] votes) {
3        // Get the number of teams (length of each vote string)
4        int numTeams = votes[0].length();
5      
6        // Create a 2D array to store vote counts for each team at each position
7        // voteCount[i][j] represents how many times team (char 'A' + i) got j-th position
8        int[][] voteCount = new int[26][numTeams + 1];
9      
10        // Count votes for each team at each position
11        for (String vote : votes) {
12            for (int position = 0; position < numTeams; ++position) {
13                char team = vote.charAt(position);
14                int teamIndex = team - 'A';
15                voteCount[teamIndex][position]++;
16            }
17        }
18      
19        // Create an array of teams to sort (using first vote as reference for all teams)
20        Character[] teams = new Character[numTeams];
21        for (int i = 0; i < numTeams; ++i) {
22            teams[i] = votes[0].charAt(i);
23        }
24      
25        // Sort teams based on voting results
26        Arrays.sort(teams, (team1, team2) -> {
27            int index1 = team1 - 'A';
28            int index2 = team2 - 'A';
29          
30            // Compare vote counts at each position (from 1st place to last)
31            for (int position = 0; position < numTeams; ++position) {
32                if (voteCount[index1][position] != voteCount[index2][position]) {
33                    // Higher count should come first (descending order)
34                    return voteCount[index2][position] - voteCount[index1][position];
35                }
36            }
37          
38            // If all positions have equal counts, sort alphabetically (ascending order)
39            return team1 - team2;
40        });
41      
42        // Build the result string from sorted teams
43        StringBuilder result = new StringBuilder();
44        for (Character team : teams) {
45            result.append(team);
46        }
47      
48        return result.toString();
49    }
50}
51
1class Solution {
2public:
3    string rankTeams(vector<string>& votes) {
4        // Get the number of teams (length of each vote string)
5        int numTeams = votes[0].size();
6      
7        // Create a 2D array to store vote counts
8        // cnt[team][position] = number of votes for team at position
9        // 26 rows for A-Z teams, 27 columns for positions (using extra space for safety)
10        array<array<int, 27>, 26> voteCount{};
11      
12        // Count votes for each team at each position
13        for (const auto& vote : votes) {
14            for (int position = 0; position < numTeams; ++position) {
15                // Increment count for the team (vote[position]) at current position
16                ++voteCount[vote[position] - 'A'][position];
17            }
18        }
19      
20        // Start with any valid vote as initial order (all votes have same teams)
21        string result = votes[0];
22      
23        // Sort teams based on voting rules
24        ranges::sort(result, [&](char teamA, char teamB) {
25            int indexA = teamA - 'A';
26            int indexB = teamB - 'A';
27          
28            // Compare vote counts position by position (higher rank positions first)
29            for (int position = 0; position < numTeams; ++position) {
30                if (voteCount[indexA][position] != voteCount[indexB][position]) {
31                    // Team with more votes at this position ranks higher
32                    return voteCount[indexA][position] > voteCount[indexB][position];
33                }
34            }
35          
36            // If all positions have equal votes, sort alphabetically
37            return teamA < teamB;
38        });
39      
40        // Return the sorted team ranking as a string
41        return string(result.begin(), result.end());
42    }
43};
44
1/**
2 * Ranks teams based on voting preferences
3 * Each vote is a string where teams are ordered by preference (first char = first choice)
4 * Teams are ranked by: 1) Most first-place votes, 2) If tied, most second-place votes, etc.
5 * If still tied, sort alphabetically
6 * 
7 * @param votes - Array of vote strings, each containing team letters in preference order
8 * @returns Final ranking of teams as a string
9 */
10function rankTeams(votes: string[]): string {
11    // Get the number of teams (length of each vote)
12    const numberOfTeams: number = votes[0].length;
13  
14    // Create a 2D array to count votes for each team at each position
15    // 26 rows for letters A-Z, numberOfTeams columns for each ranking position
16    const voteCount: number[][] = Array.from(
17        { length: 26 }, 
18        () => Array(numberOfTeams).fill(0)
19    );
20  
21    // Count votes for each team at each ranking position
22    for (const vote of votes) {
23        for (let position = 0; position < numberOfTeams; position++) {
24            // Convert character to index (A=0, B=1, ..., Z=25)
25            const teamIndex: number = vote.charCodeAt(position) - 65;
26            voteCount[teamIndex][position]++;
27        }
28    }
29  
30    // Convert first vote string to array of characters for sorting
31    const teams: string[] = votes[0].split('');
32  
33    // Sort teams based on voting results
34    teams.sort((teamA: string, teamB: string) => {
35        // Get array indices for both teams
36        const indexA: number = teamA.charCodeAt(0) - 65;
37        const indexB: number = teamB.charCodeAt(0) - 65;
38      
39        // Compare vote counts at each position (first place, second place, etc.)
40        for (let position = 0; position < numberOfTeams; position++) {
41            if (voteCount[indexA][position] !== voteCount[indexB][position]) {
42                // Higher vote count at this position wins (descending order)
43                return voteCount[indexB][position] - voteCount[indexA][position];
44            }
45        }
46      
47        // If all positions have equal votes, sort alphabetically
48        return teamA.localeCompare(teamB);
49    });
50  
51    // Join sorted array back into a string
52    return teams.join('');
53}
54

Time and Space Complexity

Time Complexity: O(n Γ— m + mΒ² Γ— log m)

  • Building the count dictionary takes O(n Γ— m) time, where we iterate through n votes and for each vote we process m characters.
  • Sorting the teams takes O(m Γ— log m) time for the sorting operation itself, but the comparison function compares the count arrays which have length m, making each comparison O(m). Therefore, sorting requires O(m Γ— log m Γ— m) = O(mΒ² Γ— log m) time.
  • The total time complexity is O(n Γ— m + mΒ² Γ— log m).

Space Complexity: O(mΒ²)

  • The cnt dictionary stores m teams, and each team has an array of length m to store the count for each position, resulting in O(m Γ— m) = O(mΒ²) space.
  • The sorted result and other variables use O(m) space, which is dominated by the O(mΒ²) term.
  • The total space complexity is O(mΒ²).

Where n is the length of votes (number of voters) and m is the number of candidates (length of votes[0]).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Edge Case: Single Vote Input

When there's only one vote in the input array (e.g., votes = ["ABC"]), the algorithm still works correctly, but many developers forget to handle this edge case in their initial implementation or testing. With a single vote, all teams have exactly 1 vote at their respective positions, so the final ranking should match the input vote exactly.

Solution: No code change needed, but ensure your test cases include single-vote scenarios:

assert rankTeams(["ABC"]) == "ABC"
assert rankTeams(["ZYX"]) == "ZYX"

2. Incorrect Tiebreaker Implementation

A critical pitfall is incorrectly implementing the alphabetical tiebreaker. Two common mistakes:

Mistake 1: Using positive ord(c) instead of negative:

# Wrong - this reverses alphabetical order when reverse=True
sorted(vote_counts, key=lambda c: (vote_counts[c], ord(c)), reverse=True)

Mistake 2: Trying to handle alphabetical sorting separately:

# Wrong - this doesn't properly integrate the tiebreaker
sorted(vote_counts, key=lambda c: vote_counts[c], reverse=True)
# Then trying to sort alphabetically afterward breaks the vote-based ordering

Solution: Always use -ord(c) in the sorting key tuple to ensure proper alphabetical ordering when reverse=True is used.

3. Assuming Fixed Team Names or Count

Some implementations hardcode assumptions about team names (e.g., only using 'A' through 'Z') or team count, which breaks when input contains different characters or varying numbers of teams.

Wrong approach:

# Hardcoding 26 teams
vote_counts = [[0] * len(votes[0]) for _ in range(26)]

Solution: Use dynamic structures like defaultdict that adapt to any team names and counts present in the input.

4. Memory Inefficiency with Sparse Teams

If votes contain only a few teams (e.g., ["AB", "BA"]), initializing storage for all possible characters wastes memory. While the provided solution handles this well with defaultdict, some developers pre-allocate arrays for all 26 letters unnecessarily.

Solution: Only create count arrays for teams that actually appear in votes, which the defaultdict approach handles automatically.

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

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More