A clear explanation of Online Election using preprocessing and binary search over vote times.
Problem Restatement
We need to implement a class called TopVotedCandidate.
We are given two arrays:
persons
timesThe i-th vote was cast for persons[i] at time times[i].
For a query time t, we need to return the candidate who was leading at time t.
Votes cast exactly at time t count.
If there is a tie, the candidate who received the most recent vote among the tied candidates wins. This is the key rule of the problem.
Input and Output
| Item | Meaning |
|---|---|
| Constructor input | persons and times |
| Query input | A time t |
| Query output | Leading candidate at time t |
| Tie rule | Most recent vote wins |
| Time array | Strictly increasing |
Class shape:
class TopVotedCandidate:
def __init__(self, persons: list[int], times: list[int]):
...
def q(self, t: int) -> int:
...Examples
Example:
persons = [0, 1, 1, 0, 0, 1, 0]
times = [0, 5, 10, 15, 20, 25, 30]Queries:
q(3) -> 0
q(12) -> 1
q(25) -> 1
q(15) -> 0
q(24) -> 0
q(8) -> 1At time 3, only the first vote has happened:
[0]So candidate 0 leads.
At time 12, votes up to time 10 have happened:
[0, 1, 1]Candidate 1 leads.
At time 25, votes up to time 25 have happened:
[0, 1, 1, 0, 0, 1]Candidates 0 and 1 both have 3 votes, but candidate 1 received the most recent vote, so candidate 1 leads.
First Thought: Recount Every Query
A direct method is to answer each query from scratch.
For a query t:
- Count all votes with
time <= t. - Track the candidate with the highest count.
- Break ties by the most recent vote.
This works, but it repeats the same counting many times.
class TopVotedCandidate:
def __init__(self, persons: list[int], times: list[int]):
self.persons = persons
self.times = times
def q(self, t: int) -> int:
count = {}
leader = -1
leader_votes = 0
for person, time in zip(self.persons, self.times):
if time > t:
break
count[person] = count.get(person, 0) + 1
if count[person] >= leader_votes:
leader = person
leader_votes = count[person]
return leaderIf there are many queries, this is too slow.
Key Insight
The leader only changes when a vote happens.
Between two vote times, the answer stays the same.
So we can precompute the leader after each vote.
Then for query t, we only need to find the latest vote time that is less than or equal to t.
Because times is sorted, we can find that position using binary search.
Preprocessing
During initialization, scan all votes from left to right.
Maintain:
count[candidate] = number of votes
leader = current leading candidate
leader_votes = votes of current leaderWhen a candidate receives a vote:
count[person] += 1If this candidate now has at least as many votes as the current leader, update the leader:
if count[person] >= leader_votes:
leader = personThe >= is important.
It handles ties correctly because the current vote is the most recent vote.
After each vote, store the leader at that time.
Query
For a query time t, find the index of the last vote time <= t.
In Python, this is:
i = bisect_right(times, t) - 1Then return:
leaders[i]where leaders[i] is the leader after the i-th vote.
Algorithm
Constructor:
- Store
times. - Create an empty vote count map.
- Create an empty
leadersarray. - Scan each vote.
- Update that candidate’s vote count.
- If the candidate ties or beats the current leader, make them the leader.
- Append the current leader to
leaders.
Query:
- Binary search for the rightmost time
<= t. - Return the leader stored at that index.
Correctness
After processing vote i, the constructor records the candidate who leads after all votes from 0 through i.
The update rule is correct because a candidate becomes leader when their count is greater than the current leader’s count. If their count is equal, they also become leader because the latest vote wins ties.
For a query time t, all votes with time <= t count, and all later votes do not count.
Since times is strictly increasing, binary search finds exactly the last vote index whose time is at most t.
The stored leader at that index is therefore the leader after exactly the votes that should count for query t.
So q(t) returns the correct candidate.
Complexity
| Operation | Time | Space |
|---|---|---|
| Constructor | O(n) | O(n) |
q(t) | O(log n) | O(1) |
The constructor stores one leader for each vote.
Each query uses binary search over times.
Implementation
from bisect import bisect_right
class TopVotedCandidate:
def __init__(self, persons: list[int], times: list[int]):
self.times = times
self.leaders = []
count = {}
leader = -1
leader_votes = 0
for person in persons:
count[person] = count.get(person, 0) + 1
if count[person] >= leader_votes:
leader = person
leader_votes = count[person]
self.leaders.append(leader)
def q(self, t: int) -> int:
i = bisect_right(self.times, t) - 1
return self.leaders[i]Code Explanation
We store the vote times for later binary search:
self.times = timesWe also store the leader after each vote:
self.leaders = []The vote count map tracks how many votes each candidate has:
count = {}For every vote, update the candidate’s count:
count[person] = count.get(person, 0) + 1Then update the leader if this candidate ties or exceeds the current leader:
if count[person] >= leader_votes:
leader = person
leader_votes = count[person]The >= handles the most recent vote tie rule.
After processing this vote, save the current leader:
self.leaders.append(leader)For a query, bisect_right gives the insertion position after all times <= t:
i = bisect_right(self.times, t) - 1So i is the last counted vote index.
Return the precomputed leader:
return self.leaders[i]Testing
def run_tests():
top = TopVotedCandidate(
[0, 1, 1, 0, 0, 1, 0],
[0, 5, 10, 15, 20, 25, 30],
)
assert top.q(3) == 0
assert top.q(12) == 1
assert top.q(25) == 1
assert top.q(15) == 0
assert top.q(24) == 0
assert top.q(8) == 1
top = TopVotedCandidate(
[1, 2, 2, 1],
[10, 20, 30, 40],
)
assert top.q(10) == 1
assert top.q(20) == 2
assert top.q(25) == 2
assert top.q(40) == 1
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Official-style sequence | Checks normal queries and tie rule |
| Query between vote times | Confirms binary search behavior |
| Query exactly at vote time | Confirms vote at t counts |
| Repeated tie changes | Confirms most recent tied candidate wins |