Online Election
MedPrompt
You are given two integer arrays persons and times. In an election, the ith vote was cast for persons[i] at time times[i].
For each query at a time t, find the person that was leading the election at time t. Votes cast at time t will count towards our query. In the case of a tie, the most recent vote (among tied candidates) wins.
Implement the TopVotedCandidate class:
TopVotedCandidate(int[] persons, int[] times)Initializes the object with thepersonsandtimesarrays.int q(int t)Returns the number of the person that was leading the election at timetaccording to the mentioned rules.
Example 1:
Input
["TopVotedCandidate", "q", "q", "q", "q", "q", "q"]
[[[0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]], [3], [12], [25], [15], [24], [8]]
Output
[null, 0, 1, 1, 0, 0, 1]
Explanation
TopVotedCandidate topVotedCandidate = new TopVotedCandidate([0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]);
topVotedCandidate.q(3); // return 0, At time 3, the votes are [0], and 0 is leading.
topVotedCandidate.q(12); // return 1, At time 12, the votes are [0,1,1], and 1 is leading.
topVotedCandidate.q(25); // return 1, At time 25, the votes are [0,1,1,0,0,1], and 1 is leading (as ties go to the most recent vote.)
topVotedCandidate.q(15); // return 0
topVotedCandidate.q(24); // return 0
topVotedCandidate.q(8); // return 1
Constraints:
1 <= persons.length <= 5000times.length == persons.length0 <= persons[i] < persons.length0 <= times[i] <= 109timesis sorted in a strictly increasing order.times[0] <= t <= 109- At most
104calls will be made toq.
Approaches
2 approaches with complexity analysis and trade-offs.
This straightforward approach simulates the election from the beginning up to the query time t for every single query. The constructor's role is minimal, simply storing the initial data. While easy to conceptualize, it's inefficient for numerous queries.
Algorithm
- In the constructor, store the
personsandtimesarrays. - In the
q(t)method:- Find the largest index
kintimessuch thattimes[k] <= tusing binary search. - Initialize an empty map
countsfor vote tallies. - Initialize
leader = -1andmaxVotes = 0. - Loop from
i = 0tok:- Increment the vote count for
persons[i]in thecountsmap. - If the new count for
persons[i]is greater than or equal tomaxVotes, updateleadertopersons[i]andmaxVotesto the new count.
- Increment the vote count for
- Return the final
leader.
- Find the largest index
Walkthrough
In this method, we avoid any pre-computation and handle all the logic within the q(t) function.
The constructor TopVotedCandidate(int[] persons, int[] times) just saves the input arrays for later use.
The query function q(int t) performs the following steps:
- It first determines how many votes to consider. Given the query time
t, it finds the indexkof the last vote cast at or beforet. This can be done efficiently with a binary search on the sortedtimesarray. - It then simulates the election up to vote
k. A hash map is used to keep track of the vote count for each person. - It iterates through the votes from index
0tok. For each votei, it increments the count forpersons[i]. - To correctly handle the tie-breaking rule (the most recent vote wins), it also keeps track of the current leader. After updating a person's vote count, if their new count is greater than or equal to the current leader's count, they become the new leader. The 'greater than or equal' condition elegantly handles ties by favoring the person who just voted.
- After processing all votes up to index
k, the final leader is returned as the result.
import java.util.HashMap;import java.util.Map; class TopVotedCandidate { int[] persons; int[] times; public TopVotedCandidate(int[] persons, int[] times) { this.persons = persons; this.times = times; } public int q(int t) { // Find the index of the last vote at or before time t using binary search. int low = 0, high = times.length - 1; int lastVoteIndex = -1; while (low <= high) { int mid = low + (high - low) / 2; if (times[mid] <= t) { lastVoteIndex = mid; low = mid + 1; } else { high = mid - 1; } } if (lastVoteIndex == -1) { return -1; // Should not happen based on constraints } // Simulate the election up to this point Map<Integer, Integer> counts = new HashMap<>(); int leader = -1; int maxVotes = 0; for (int i = 0; i <= lastVoteIndex; i++) { int person = persons[i]; counts.put(person, counts.getOrDefault(person, 0) + 1); if (counts.get(person) >= maxVotes) { maxVotes = counts.get(person); leader = person; } } return leader; }}Complexity
Time
O(N * Q), where N is the number of votes and Q is the number of queries. Each query involves a binary search (O(log N)) followed by a loop that can run up to N times (O(N)). Thus, each query is O(N), leading to a total time of O(N * Q).
Space
O(P) for each query, where P is the number of unique persons (P <= N). This space is for the `counts` map used during the simulation within each query.
Trade-offs
Pros
Simple to understand and implement.
The constructor is very fast, O(1).
Cons
Highly inefficient for a large number of queries.
Repeats the same counting work for overlapping time intervals.
Likely to result in a 'Time Limit Exceeded' error in a competitive programming context.
This optimized approach is based on the insight that the election leader can only change when a vote is cast. We can pre-calculate the leader at each of these specific time points and store them. Queries can then be answered very quickly by finding the relevant time point using binary search and retrieving the precomputed leader.
Algorithm
- In the constructor:
- Initialize a
leadersarray, acountsmap,leader = -1, andmaxVotes = 0. - Loop from
i = 0ton-1:- Update the vote count for
persons[i]. - If
persons[i]'s new count is>= maxVotes, updateleaderandmaxVotes. - Store the current
leaderinleaders[i].
- Update the vote count for
- Initialize a
- In the
q(t)method:- Use binary search on the
timesarray to find the largest indexiwheretimes[i] <= t. - Return
leaders[i].
- Use binary search on the
Walkthrough
The core idea is to trade a one-time computation cost in the constructor for extremely fast queries.
Constructor TopVotedCandidate(int[] persons, int[] times):
- We precompute the leader at every time
tpresent in thetimesarray. - We use an array,
leaders, of the same size aspersonsto store the leader at each corresponding time intimes. - We iterate through the votes chronologically from
i = 0ton-1. We use a hash mapcountsto maintain a running tally of votes for each person. - We also track the current
leaderand theirmaxVotes. At each stepi, we process the vote forpersons[i], update their count, and check if they become the new leader. A personpbecomes the leader if their vote count surpasses the currentmaxVotes, or if it equalsmaxVotes(due to the tie-breaking rule favoring the most recent vote). - The leader at time
times[i]is then stored inleaders[i].
Query q(int t):
- For a query time
t, the leader is the same as the leader at the time of the last vote cast at or beforet. - Our goal is to find the largest index
isuch thattimes[i] <= t. - Since the
timesarray is sorted, we can perform a binary search on it to find this indexiin logarithmic time. - Once index
iis found, the answer is simply the precomputed valueleaders[i].
import java.util.HashMap;import java.util.Map; class TopVotedCandidate { private int[] leaders; private int[] times; public TopVotedCandidate(int[] persons, int[] times) { this.times = times; int n = persons.length; this.leaders = new int[n]; Map<Integer, Integer> counts = new HashMap<>(); int leader = -1; int maxVotes = 0; for (int i = 0; i < n; i++) { int p = persons[i]; counts.put(p, counts.getOrDefault(p, 0) + 1); if (counts.get(p) >= maxVotes) { maxVotes = counts.get(p); leader = p; } leaders[i] = leader; } } public int q(int t) { // Binary search to find the index of the latest time <= t int low = 0, high = times.length - 1; int ansIdx = 0; // The index of the leader while (low <= high) { int mid = low + (high - low) / 2; if (times[mid] <= t) { ansIdx = mid; low = mid + 1; } else { high = mid - 1; } } return leaders[ansIdx]; }}Complexity
Time
O(N + Q * log N). The constructor takes O(N) time to iterate through all votes and precompute leaders. Each of the Q queries takes O(log N) time for the binary search.
Space
O(N). We need O(N) space for the `leaders` array and to store the `times` array. The `counts` map used during precomputation also requires O(P) space, where P is the number of unique persons (P <= N).
Trade-offs
Pros
Extremely fast query time, making it suitable for a large number of queries.
The expensive computation is performed only once.
Overall efficient solution that passes strict time limits.
Cons
Requires O(N) extra space to store precomputed results.
The constructor has a higher upfront cost (O(N)) compared to the brute-force approach.
Solutions
Solution
class TopVotedCandidate { private int [] times ; private int [] wins ; public TopVotedCandidate ( int [] persons , int [] times ) { int n = persons . length ; int mx = 0 , cur = 0 ; this . times = times ; wins = new int [ n ]; int [] counter = new int [ n ]; for ( int i = 0 ; i < n ; ++ i ) { int p = persons [ i ]; if (++ counter [ p ] >= mx ) { mx = counter [ p ]; cur = p ; } wins [ i ] = cur ; } } public int q ( int t ) { int left = 0 , right = wins . length - 1 ; while ( left < right ) { int mid = ( left + right + 1 ) >>> 1 ; if ( times [ mid ] <= t ) { left = mid ; } else { right = mid - 1 ; } } return wins [ left ]; } } /** * Your TopVotedCandidate object will be instantiated and called as such: * TopVotedCandidate obj = new TopVotedCandidate(persons, times); * int param_1 = obj.q(t); */class TopVotedCandidate { public: vector < int > times ; vector < int > wins ; TopVotedCandidate ( vector < int >& persons , vector < int >& times ) { int n = persons . size (); wins . resize ( n ); int mx = 0 , cur = 0 ; this -> times = times ; vector < int > counter ( n ); for ( int i = 0 ; i < n ; ++ i ) { int p = persons [ i ]; if ( ++ counter [ p ] >= mx ) { mx = counter [ p ]; cur = p ; } wins [ i ] = cur ; } } int q ( int t ) { int left = 0 , right = wins . size () - 1 ; while ( left < right ) { int mid = left + right + 1 >> 1 ; if ( times [ mid ] <= t ) left = mid ; else right = mid - 1 ; } return wins [ left ]; } }; /** * Your TopVotedCandidate object will be instantiated and called as such: * TopVotedCandidate* obj = new TopVotedCandidate(persons, times); * int param_1 = obj->q(t); */class TopVotedCandidate : def __init__ ( self , persons : List [ int ], times : List [ int ]): mx = cur = 0 counter = Counter () self . times = times self . wins = [] for i , p in enumerate ( persons ): counter [ p ] += 1 if counter [ p ] >= mx : mx , cur = counter [ p ], p self . wins . append ( cur ) def q ( self , t : int ) -> int : left , right = 0 , len ( self . wins ) - 1 while left < right : mid = ( left + right + 1 ) >> 1 if self . times [ mid ] <= t : left = mid else : right = mid - 1 return self . wins [ left ] # Your TopVotedCandidate object will be instantiated and called as such: # obj = TopVotedCandidate(persons, times) # param_1 = obj.q(t)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.