LeetCode 2820 - Election Results
The Votes table records every voter-candidate selection. A voter may vote for multiple candidates, vote for exactly one candidate, or choose not to vote at all. The important detail is that every voter contributes a total voting weight of exactly 1.
Difficulty: 🟡 Medium
Topics: —
Solution
Problem Understanding
The Votes table records every voter-candidate selection. A voter may vote for multiple candidates, vote for exactly one candidate, or choose not to vote at all. The important detail is that every voter contributes a total voting weight of exactly 1.
If a voter selects:
- One candidate, that candidate receives
1.0vote. - Two candidates, each candidate receives
0.5votes. - Three candidates, each candidate receives
1/3votes. - No candidate (
candidate IS NULL), nobody receives any portion of that vote.
The task is to compute the total vote count received by every candidate after all vote splitting has been applied. Once all candidate totals have been calculated, we must identify the maximum vote total and return every candidate whose total equals that maximum value.
The output should contain a single column, candidate, listing all winners in ascending alphabetical order.
The input represents a collection of voting records. Multiple rows can belong to the same voter because a voter may support multiple candidates. The primary key (voter, candidate) guarantees that the same voter cannot vote for the same candidate twice.
An important observation is that vote distribution depends on the number of candidates selected by each voter. Therefore, before calculating candidate totals, we must know how many candidates each voter selected.
Several edge cases must be handled correctly:
- A voter may have only a
NULLcandidate entry, meaning they participated in the election process but cast no vote. - Multiple candidates may tie for first place.
- Every voter may vote for a different number of candidates.
- Fractional vote values must be computed accurately.
- Candidates receiving votes from many split ballots must have all contributions summed correctly.
Approaches
Brute Force
A straightforward approach is to process each candidate separately.
For every candidate, iterate through all rows and determine how much vote contribution each row provides. To compute a row's contribution, we must count how many candidates the corresponding voter selected. This counting step may require scanning the table repeatedly.
This approach is correct because every candidate's total is computed by explicitly evaluating every possible vote contribution. However, it performs redundant work. The same voter's candidate count may be recomputed many times, causing unnecessary repeated scans of the table.
As the dataset grows, repeatedly recalculating voter selections becomes increasingly expensive.
Key Insight
The critical observation is that a voter's contribution depends only on the number of non-null candidates they selected.
If a voter selected k candidates, each selected candidate receives exactly 1/k votes.
Therefore:
- Compute
kfor every voter. - For every non-null vote record, contribute
1/kto the selected candidate. - Aggregate candidate totals.
- Find the maximum total.
- Return all candidates matching that maximum.
In SQL, this naturally translates into:
- A grouping operation to count candidate selections per voter.
- A join back to the original table.
- A second grouping operation to compute candidate totals.
- A final filter selecting candidates whose totals equal the maximum score.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Repeatedly recomputes voter selection counts |
| Optimal | O(n) | O(n) | Compute voter counts once, then aggregate candidate totals |
Algorithm Walkthrough
Optimal SQL Algorithm
- Group rows by
voterand count the number of non-null candidates selected by each voter. This gives the denominator needed for vote splitting. - Join this voter-count result back to the original
Votestable. Every vote record now knows how many candidates its voter selected. - Ignore rows whose candidate is
NULL, because they contribute no votes to any candidate. - For every non-null vote record, compute its contribution as:
1 / candidate_count
5. Group by candidate and sum all contributions. The result is the final vote total for every candidate.
6. Find the largest vote total among all candidates.
7. Return every candidate whose total equals that maximum value.
8. Sort the resulting candidate names in ascending order.
Why it works
Every voter contributes exactly one unit of voting power. If a voter selects k candidates, assigning 1/k to each candidate ensures the total distributed vote remains exactly 1. Summing these contributions across all voters produces the correct election totals. Selecting candidates whose totals equal the maximum value therefore identifies exactly the election winner or winners.
Python Solution
Although this is a SQL problem on LeetCode, the following Python implementation demonstrates the underlying algorithm.
from collections import defaultdict
from typing import List, Tuple
class Solution:
def electionResults(
self,
votes: List[Tuple[str, str | None]]
) -> List[str]:
voter_counts = defaultdict(int)
for voter, candidate in votes:
if candidate is not None:
voter_counts[voter] += 1
candidate_scores = defaultdict(float)
for voter, candidate in votes:
if candidate is not None:
candidate_scores[candidate] += 1.0 / voter_counts[voter]
max_score = max(candidate_scores.values())
winners = [
candidate
for candidate, score in candidate_scores.items()
if abs(score - max_score) < 1e-9
]
return sorted(winners)
The implementation first counts how many candidates each voter selected. This count determines the fraction allocated to each chosen candidate.
Next, every non-null vote contributes 1 / voter_count to the candidate's accumulated score.
After all scores are computed, the maximum score is determined. Every candidate matching that maximum score is collected and returned in sorted order.
Go Solution
package main
import (
"math"
"sort"
)
func electionResults(votes [][2]string) []string {
voterCounts := make(map[string]int)
for _, vote := range votes {
voter := vote[0]
candidate := vote[1]
if candidate != "" {
voterCounts[voter]++
}
}
candidateScores := make(map[string]float64)
for _, vote := range votes {
voter := vote[0]
candidate := vote[1]
if candidate != "" {
candidateScores[candidate] += 1.0 / float64(voterCounts[voter])
}
}
maxScore := 0.0
for _, score := range candidateScores {
if score > maxScore {
maxScore = score
}
}
var winners []string
for candidate, score := range candidateScores {
if math.Abs(score-maxScore) < 1e-9 {
winners = append(winners, candidate)
}
}
sort.Strings(winners)
return winners
}
The Go version follows exactly the same logic as the Python solution. Maps are used for both voter counts and candidate score aggregation. Floating point comparisons use a small epsilon to avoid precision issues when comparing vote totals.
SQL Solution
The actual LeetCode submission should be written in SQL.
WITH voter_counts AS (
SELECT
voter,
COUNT(candidate) AS cnt
FROM Votes
GROUP BY voter
),
candidate_votes AS (
SELECT
v.candidate,
SUM(1.0 / vc.cnt) AS total_votes
FROM Votes v
JOIN voter_counts vc
ON v.voter = vc.voter
WHERE v.candidate IS NOT NULL
GROUP BY v.candidate
)
SELECT candidate
FROM candidate_votes
WHERE total_votes = (
SELECT MAX(total_votes)
FROM candidate_votes
)
ORDER BY candidate;
The first CTE computes how many candidates each voter selected. The second CTE calculates total votes received by each candidate after vote splitting. Finally, candidates matching the maximum vote total are returned in ascending order.
Worked Examples
Example 1
Input:
| voter | candidate |
|---|---|
| Kathy | NULL |
| Charles | Ryan |
| Charles | Christine |
| Charles | Kathy |
| Benjamin | Christine |
| Anthony | Ryan |
| Edward | Ryan |
| Terry | NULL |
| Evelyn | Kathy |
| Arthur | Christine |
Step 1: Count selections per voter
| voter | candidate count |
|---|---|
| Charles | 3 |
| Benjamin | 1 |
| Anthony | 1 |
| Edward | 1 |
| Evelyn | 1 |
| Arthur | 1 |
| Kathy | 0 |
| Terry | 0 |
Step 2: Distribute votes
| voter | candidate | contribution |
|---|---|---|
| Charles | Ryan | 1/3 |
| Charles | Christine | 1/3 |
| Charles | Kathy | 1/3 |
| Benjamin | Christine | 1 |
| Anthony | Ryan | 1 |
| Edward | Ryan | 1 |
| Evelyn | Kathy | 1 |
| Arthur | Christine | 1 |
Step 3: Aggregate candidate totals
| candidate | total |
|---|---|
| Ryan | 1/3 + 1 + 1 = 2.3333 |
| Christine | 1/3 + 1 + 1 = 2.3333 |
| Kathy | 1/3 + 1 = 1.3333 |
Step 4: Determine winners
Maximum total:
| candidate | total |
|---|---|
| Christine | 2.3333 |
| Ryan | 2.3333 |
Output:
| candidate |
|---|
| Christine |
| Ryan |
Complexity Analysis
SQL Solution
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each row participates in a constant number of grouping and aggregation operations |
| Space | O(n) | Aggregation tables store voter counts and candidate totals |
The solution performs two main aggregation passes over the data. Modern database engines implement grouping with hashing or sorting, making the overall work proportional to the size of the input table.
Test Cases
def election_results(votes):
from collections import defaultdict
voter_counts = defaultdict(int)
for voter, candidate in votes:
if candidate is not None:
voter_counts[voter] += 1
candidate_scores = defaultdict(float)
for voter, candidate in votes:
if candidate is not None:
candidate_scores[candidate] += 1.0 / voter_counts[voter]
max_score = max(candidate_scores.values())
return sorted(
candidate
for candidate, score in candidate_scores.items()
if abs(score - max_score) < 1e-9
)
assert election_results([
("Kathy", None),
("Charles", "Ryan"),
("Charles", "Christine"),
("Charles", "Kathy"),
("Benjamin", "Christine"),
("Anthony", "Ryan"),
("Edward", "Ryan"),
("Terry", None),
("Evelyn", "Kathy"),
("Arthur", "Christine")
]) == ["Christine", "Ryan"] # official example
assert election_results([
("A", "John")
]) == ["John"] # single voter, single candidate
assert election_results([
("A", "John"),
("B", "Mary")
]) == ["John", "Mary"] # perfect tie
assert election_results([
("A", "John"),
("A", "Mary")
]) == ["John", "Mary"] # split vote equally
assert election_results([
("A", None),
("B", "John")
]) == ["John"] # abstaining voter
assert election_results([
("A", "John"),
("A", "Mary"),
("A", "Bob"),
("B", "John")
]) == ["John"] # fractional plus full vote
assert election_results([
("A", "John"),
("B", "John"),
("C", "Mary"),
("D", "Mary")
]) == ["John", "Mary"] # tie after aggregation
Test Summary
| Test | Why |
|---|---|
| Official example | Validates vote splitting and tie handling |
| Single voter | Smallest meaningful election |
| Two candidates tie | Confirms multiple winners are returned |
| One voter selects two candidates | Verifies equal vote distribution |
| Abstaining voter | Ensures NULL votes contribute nothing |
| Fractional plus full votes | Tests aggregation of mixed vote weights |
| Aggregate tie | Verifies maximum score filtering |
Edge Cases
Voters Who Do Not Vote
A voter may appear only with a NULL candidate. Such voters contribute no vote to any candidate. A common bug is accidentally including these rows when computing candidate totals. The implementation explicitly ignores NULL candidates during vote distribution.
Multiple Candidates Selected by One Voter
A voter may support several candidates. The most common mistake is counting each selection as a full vote, which would allow a voter to contribute more than one total vote. The solution first computes the number of selected candidates and allocates exactly 1/k to each, preserving the voter's total voting power.
Multiple Winners
Several candidates may finish with exactly the same vote total. Returning only one candidate would be incorrect. After computing all totals, the solution identifies the maximum score and returns every candidate whose score matches that value.
Fractional Vote Totals
Because votes are split, totals may contain repeating decimals such as 1/3. Comparing floating point values carelessly can produce incorrect results. The implementation uses tolerance-based comparisons in procedural languages, while the SQL solution relies on exact aggregation of identical expressions and comparison against the computed maximum from the same aggregation result.
All Support Concentrated on One Candidate
If every voter ultimately contributes to a single candidate, that candidate should be returned alone. The aggregation logic naturally handles this case because the maximum total belongs uniquely to that candidate.