文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:在线选举
出处:911. 在线选举
难度
7 级
题目描述
要求
给定两个整数数组 persons \texttt{persons} persons 和 times \texttt{times} times。在选举中,第 i \texttt{i} i 张票在时刻 times[i] \texttt{times[i]} times[i] 投给候选人 persons[i] \texttt{persons[i]} persons[i]。
对于发生在时刻 t \texttt{t} t 的每个查询,需要找出 t \texttt{t} t 时刻在选举中领先的候选人的编号。在 t \texttt{t} t 时刻投出的选票也将被计入查询中。在平局的情况下,票数最多的候选人中最近获得投票的候选人将会获胜。
实现 TopVotedCandidate \texttt{TopVotedCandidate} TopVotedCandidate 类:
- TopVotedCandidate(int[] persons, int[] times) \texttt{TopVotedCandidate(int[] persons, int[] times)} TopVotedCandidate(int[] persons, int[] times) 使用 persons \texttt{persons} persons 和 times \texttt{times} times 数组初始化对象。
- int q(int t) \texttt{int q(int t)} int q(int t) 根据前面描述的规则,返回时刻 t \texttt{t} t 在选举中领先的候选人的编号。
示例
示例 1:
输入:
["TopVotedCandidate", "q", "q", "q", "q", "q", "q"] \texttt{["TopVotedCandidate", "q", "q", "q", "q", "q", "q"]} ["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]] \texttt{[[[0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]], [3], [12], [25], [15], [24], [8]]} [[[0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]], [3], [12], [25], [15], [24], [8]]
输出:
[null, 0, 1, 1, 0, 0, 1] \texttt{[null, 0, 1, 1, 0, 0, 1]} [null, 0, 1, 1, 0, 0, 1]
解释:
TopVotedCandidate topVotedCandidate = new TopVotedCandidate([0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]); \texttt{TopVotedCandidate topVotedCandidate = new TopVotedCandidate([0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]);} TopVotedCandidate topVotedCandidate = new TopVotedCandidate([0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]);
topVotedCandidate.q(3); \texttt{topVotedCandidate.q(3);} topVotedCandidate.q(3); // 返回 0 \texttt{0} 0,在时刻 3 \texttt{3} 3,票数分布为 [0] \texttt{[0]} [0],编号为 0 \texttt{0} 0 的候选人领先。
topVotedCandidate.q(12); \texttt{topVotedCandidate.q(12);} topVotedCandidate.q(12); // 返回 1 \texttt{1} 1,在时刻 12 \texttt{12} 12,票数分布为 [0,1,1] \texttt{[0,1,1]} [0,1,1],编号为 1 \texttt{1} 1 的候选人领先。
topVotedCandidate.q(25); \texttt{topVotedCandidate.q(25);} topVotedCandidate.q(25); // 返回 1 \texttt{1} 1,在时刻 25 \texttt{25} 25,票数分布为 [0,1,1,0,0,1] \texttt{[0,1,1,0,0,1]} [0,1,1,0,0,1],编号为 1 \texttt{1} 1 的候选人领先(在平局的情况下, 1 \texttt{1} 1 是最近获得投票的候选人)。
topVotedCandidate.q(15); \texttt{topVotedCandidate.q(15);} topVotedCandidate.q(15); // 返回 0 \texttt{0} 0
topVotedCandidate.q(24); \texttt{topVotedCandidate.q(24);} topVotedCandidate.q(24); // 返回 0 \texttt{0} 0
topVotedCandidate.q(8); \texttt{topVotedCandidate.q(8);} topVotedCandidate.q(8); // 返回 1 \texttt{1} 1
数据范围
- 1 ≤ persons.length ≤ 5000 \texttt{1} \le \texttt{persons.length} \le \texttt{5000} 1≤persons.length≤5000
- times.length = persons.length \texttt{times.length} = \texttt{persons.length} times.length=persons.length
- 0 ≤ persons[i] < persons.length \texttt{0} \le \texttt{persons[i]} < \texttt{persons.length} 0≤persons[i]<persons.length
- 0 ≤ times[i] ≤ 10 9 \texttt{0} \le \texttt{times[i]} \le \texttt{10}^\texttt{9} 0≤times[i]≤109
- times \texttt{times} times 严格升序排序
- times[0] ≤ t ≤ 10 9 \texttt{times[0]} \le \texttt{t} \le \texttt{10}^\texttt{9} times[0]≤t≤109
- 最多调用 10 4 \texttt{10}^\texttt{4} 104 次 q \texttt{q} q
解法
思路和算法
查询时刻 t t t 在选举中领先的候选人时,需要找到不晚于时刻 t t t 的最后一次投票时刻,并计算该时刻每个候选人的票数,得到在选举中领先的候选人。
为了快速得到选举中特定时刻领先的候选人,需要在初始化时预处理投票信息,对于每张选票,更新候选人的票数,并记录该时刻领先的候选人。在预处理之后,每次查询时,对于给定的时刻 t t t,需要找到不晚于时刻 t t t 的最后一次投票时刻,得到该时刻领先的候选人。由于数组 times \textit{times} times 严格升序,因此可以使用二分查找的方式找到特定时刻。
为了记录预处理信息,需要维护时刻数组、每个时刻领先的候选人数组(与时刻数组等长)以及每个候选人的票数。
构造方法中预处理投票信息。用给定的数组 times \textit{times} times 作为时刻数组,并维护当前领先的候选人编号。同时遍历给定的数组 persons \textit{persons} persons 和 times \textit{times} times,对于每个时刻的选票,更新该选票投给的候选人的票数,如果该候选人的票数大于等于当前领先的候选人的票数(由于平局时最近获得选票的候选人领先,因此也需要考虑票数相等的情况),则更新当前领先的候选人编号。遍历结束之后,即可得到每个时刻领先的候选人数组。
对于调用 q q q 的操作,首先使用二分查找得到不晚于时刻 t t t 的最后一次投票时刻,然后在每个时刻领先的候选人数组中找到该时刻领先的候选人并返回。
用 low \textit{low} low 和 high \textit{high} high 分别表示二分查找的下界和上界。由于需要在时刻数组中查找小于等于 t t t 的最大元素所在下标,且 t t t 不小于时刻数组的首个元素(即下标 0 0 0 处的元素),因此初始时 low \textit{low} low 和 high \textit{high} high 分别为时刻数组的最小下标和最大下标。每次查找时,取 mid \textit{mid} mid 为 low \textit{low} low 和 high \textit{high} high 的平均数向上取整,得到时刻数组中下标 mid \textit{mid} mid 处的元素并与时刻 t t t 比较,调整查找的下标范围。
-
如果时刻数组中下标 mid \textit{mid} mid 处的元素小于等于 t t t,则不晚于时刻 t t t 的最后一次投票时刻所在下标大于等于 mid \textit{mid} mid,因此在下标范围 [ mid , high ] [\textit{mid}, \textit{high}] [mid,high] 中继续查找。
-
如果时刻数组中下标 mid \textit{mid} mid 处的元素大于 t t t,则不晚于时刻 t t t 的最后一次投票时刻所在下标小于 mid \textit{mid} mid,因此在下标范围 [ low , mid − 1 ] [\textit{low}, \textit{mid} - 1] [low,mid−1] 中继续查找。
当 low = high \textit{low} = \textit{high} low=high 时,查找结束,此时 low \textit{low} low 即为不晚于时刻 t t t 的最后一次投票时刻所在下标。每个时刻领先的候选人数组中的下标 low \textit{low} low 处的元素即为时刻 t t t 在选举中领先的候选人的编号。
代码
class TopVotedCandidate {int[] times;int[] tops;Map<Integer, Integer> votes;public TopVotedCandidate(int[] persons, int[] times) {this.times = times;int length = persons.length;this.tops = new int[length];this.votes = new HashMap<Integer, Integer>();int top = -1;for (int i = 0; i < length; i++) {int person = persons[i], time = times[i];votes.put(person, votes.getOrDefault(person, 0) + 1);if (i == 0 || votes.get(person) >= votes.get(top)) {top = person;}tops[i] = top;}}public int q(int t) {int low = 0, high = times.length - 1;while (low < high) {int mid = low + (high - low + 1) / 2;if (times[mid] <= t) {low = mid;} else {high = mid - 1;}}return tops[low];}
}
复杂度分析
-
时间复杂度:构造方法的时间复杂度是 O ( n ) O(n) O(n),每次查询的时间复杂度是 O ( log n ) O(\log n) O(logn),其中 n n n 是数组 persons \textit{persons} persons 和 times \textit{times} times 的长度。构造方法需要遍历数组 persons \textit{persons} persons 和 times \textit{times} times 预处理投票信息,需要 O ( n ) O(n) O(n) 的时间。每次查询时,二分查找定位到不晚于时刻 t t t 的最后一次投票时刻需要 O ( log n ) O(\log n) O(logn) 的时间,得到该时刻在选举中领先的候选人的编号的时间是 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 persons \textit{persons} persons 和 times \textit{times} times 的长度。存储时刻数组、每个时刻领先的候选人数组以及每个候选人的票数需要 O ( n ) O(n) O(n) 的空间。