题目来源:力扣
题目描述:
在选举中,第 i 张票是在时间为 times[i] 时投给 persons[i] 的。
现在,我们想要实现下面的查询函数: TopVotedCandidate.q(int t) 将返回在 t 时刻主导选举的候选人的编号。
在 t 时刻投出的选票也将被计入我们的查询之中。在平局的情况下,最近获得投票的候选人将会获胜。
最多5000候选人.
审题:
此题可分解为两个子问题:
- 记录每一次投票后的领先候选人
- 搜索距离给定时刻t最近的投票时刻 t ′ t' t′, t ′ < t t'<t t′<t
求解方案:
对于问题1, 我们可以使用优先队列纪录维护所有候选人的得票结果,优先队列可以在O(lgN)时间复杂度内返回当前领先的侯选人.
对于问题2,由于time数组为递增数组, 因此可以使用二分搜索搜索t时刻对应的投票时刻.
基于优先队列的java实现
class TopVotedCandidate {
//创建Person类维护候选人的编号, 当前得票,以及最近投票时刻
class Person{
int id;
int count;
int recentVoteTime;
public Person(int id, int count, int recentVoteTime){
this.id = id;
this.count = count;
this.recentVoteTime = recentVoteTime;
}
}
//创建候选人的比较器用于优先队列中候选人大小比较
class PersonCmp implements Comparator<Person>{
public int compare(Person p1, Person p2){
//由于优先队列中最小元素优先级最高,因此此处得票多的候选人小于得票少的候选人,
// 同等得票时, 投票时间更晚的人小于更早的人
if(p1.count != p2.count){
return Integer.compare(p2.count, p1.count);
}
else
return Integer.compare(p2.recentVoteTime, p1.recentVoteTime);
}
}
int[] winId; //纪录投票更新后领先的候选人ID
int[] voteTime; //复制投票时刻
public TopVotedCandidate(int[] persons, int[] times) {
winId = new int[times.length];
Person[] IndexedPersons = new Person[5000]; //最多5000候选人
voteTime = new int[times.length];
PriorityQueue<Person> pq = new PriorityQueue<>(10, new PersonCmp()); //创建优先队列
for(int i = 0; i < persons.length; i++){
//更新候选人得票
int id = persons[i];
if(IndexedPersons[id] == null){
IndexedPersons[id] = new Person(id, 1, times[i]);
}
else{
pq.remove(IndexedPersons[id]);
IndexedPersons[id] = new Person(id, IndexedPersons[id].count+1, times[i]);
}
pq.offer(IndexedPersons[id]);
winId[i] = pq.peek().id;
}
System.arraycopy(times, 0, voteTime, 0, times.length); //复制投票时间数组
}
public int q(int t) {
//二分搜索t对应的最近投票时刻
int lo = 0;
int hi = voteTime.length;
int mid;
while(lo < hi){
mid = lo + (hi-lo)/2;
if(voteTime[mid] == t)
return winId[mid];
else if(voteTime[mid] > t)//如果voteTime[mid] > t, 则最近投票时刻一定在[lo, mid)范围中
hi = mid;
else //最近投票时刻可能在[mid+1, hi)中,也可能等于mid
lo = mid+1;
}
//如果未搜索到,则投票时刻为lo-1
return winId[lo-1];
}
}
在看了其他人写的题解后,发现这道题使用优先队列还是有点复杂了,可谓杀鸡用牛刀.由于候选人票数仅会增加,因此可以在遍历过程中纪录领先候选人的得票数,如果当前投票足以改变领先候选人,则更新,否则更新得票数即可.这样一来,每次投票后搜索领先候选人的时间复杂度仅为O(1), 而非O(lgN), 因此不仅实现简单, 算法效率从理论上也更高了.
java实现:
class TopVotedCandidate {
int[] winId; //纪录投票更新后领先的候选人ID
int[] voteTime;
public TopVotedCandidate(int[] persons, int[] times) {
winId = new int[times.length];
voteTime = new int[times.length];
int[] personVote = new int[5000]; //最多5000个候选人.
//初始化
int maxCount = 1;
int maxId = persons[0];
personVote[maxId] = maxCount;
for(int i = 1; i < persons.length; i++){
personVote[persons[i]]++; //得票+1;
if(personVote[persons[i]] >= maxCount){
maxCount = personVote[persons[i]];
maxId = persons[i]; //更新maxId
}
winId[i] = maxId;
}
System.arraycopy(times, 0, voteTime, 0, times.length); //复制投票时间数组
}
public int q(int t) {
//二分搜索t对应的最近投票时刻
int lo = 0;
int hi = voteTime.length;
int mid;
while(lo < hi){
mid = lo + (hi-lo)/2;
if(voteTime[mid] == t)
return winId[mid];
else if(voteTime[mid] > t)
hi = mid;
else
lo = mid+1;
}
return winId[lo-1];
}
}