周六!放松!共勉!
今天的题目编号挺特殊的
1、先来看题
今天的每日一题,中等难度。题目稍微有些绕,但理清重点后还是很好理解。
我对自己做中等题目的要求是,5分钟内能有明确的思路。
不过今天挺幸运的,也可能是接触过较多类似的题,在读完题后就有了完整的思路。
2、审题
今天的题目只写了一个case,而却罗里吧嗦地列了一长串解释,但重点却寥寥无几。
简单总结一下:
- 编写一个类,其构造函数传入persons[]和times[]。
q(int t)
方法实现每次调用时,返回t时刻的胜选人。 time[]
严格递增,在time[i]
时刻的票投给了persons[i]
,一次仅投1票。0 <= persons[i] < persons.length
,意味着候选人的数量不可控。- 在平局的情况下,最近获得投票的候选人将会获胜。
- 询问(调用
q()
)时的t
,不一定出现在time[]
数组中(做过的朋友会发现,甚至有超出了time[]
中的最大值的) - 测试用例会重复调用多次
q()
,最多调用 104 次 。
3、思路
依旧没有陷阱(我还真是做脑筋急转弯做怕了 )。
辣么来说下自己的思路吧。
由于候选人是复数个的,于是我们必须记录每次投票后,投给的候选人的总票数。
好在每次投票仅投给一个人,这样会让每次投票的数据变化很好掌控。我们以Map来记录每个候选人的得票数。
由于time[]
严格递增,所以我们不用分别记录某个时刻的得票数,仅以一个Map全局记录便可。
由于q()
会调用多次,且每次传入的t
不一定是time[]
中的数据,所以我决定用一个数组来记录所有时刻的胜选者。
好了,胜利的方程式已经确认,开工吧!
4、撸代码
class TopVotedCandidate {
private int maxTime;
private int[] question;
public TopVotedCandidate(int[] persons, int[] times) {
//记录最大时间
maxTime = times[times.length - 1];
//初始化数组
question = new int[maxTime + 1];
Arrays.fill(question,-1);
Map<Integer, Integer> votes = new HashMap<>();
int maxVote = 0;
for (int i = 0; i < times.length; i++) {
//i时刻投给了persons[i]
int person = persons[i];
//i时刻,person的得票数
int vote = votes.getOrDefault(person, 0) + 1;
//票数反超时
if (vote >= maxVote) {
maxVote = vote;
//记录此时的胜者
question[times[i]] = person;
}
votes.put(person, vote);
}
int prevWinner = 0;
for (int t = 0; t < question.length; t++) {
if (question[t] != -1) {
prevWinner = question[t];
} else {
question[t] = prevWinner;
}
}
}
public int q(int t) {
//当询问时间超过最大时间时,返回栈的最后一个记录
if (t > maxTime) {
return question[question.length - 1];
}
return question[t];
}
}
整体实现确实是顺风顺水,不过额外有些case让我大跌眼镜,所以中途还是改过几版。
这是我第一次成功提交的版本。保留了完整的思路和注释。
5、解读
就如同我在思路中写到的,votes
是一个Map,我用它记录了所有候选者的得票数。
在遍历time[]
时,以maxVote
记录当前时间的最高得票数。
并在胜选者发生变化时,在question
数组的当前时间下记录了候选者的信息。
question[times[i]] = person
注意,由于平票时,最近得票的候选者胜出。所以判断条件为
if (vote >= maxVote)
之后的核心要点就是
int prevWinner = 0;
for (int t = 0; t < question.length; t++) {
if (question[t] != -1) {
prevWinner = question[t];
} else {
question[t] = prevWinner;
}
}
由于q()
方法会调用多次,于是我决定用question
数组记录下每个时刻的胜选人。
这样在调用q()
方法时,就能直接从对应数组下标取出当前的胜选者,从而让此方法的时间复杂度变为O(1)
public int q(int t) {
//当询问时间超过最大时间时,返回栈的最后一个记录
if (t > maxTime) {
return question[question.length - 1];
}
return question[t];
}
这里多了一个判断,是我在提交后发现case中有出现t
超过times[]
中的最大值的情况。
于是我用一个全局变量maxTime
记录了times[]
的最大值。并在t
超过maxTime
时,直接返回question[]
的最后一位。
6、提交
时间排名接近100%。
由于我是以空间换时间,所以在内存上的消耗会较大笑死,我才不在意空间复杂度呢 。不过75%的排名比我预期的要高挺多。
7、咀嚼
初始化时的时间复杂度为O(N+M),N为times[]
的长度,M为question[]
的长度,也就是times[]
中的最大值。
而查询时的时间复杂度为O(1)
之后我自然查看了官解和一些其他大牛的解法。
由于初始化时,官解并没有初始化question[]
,所以时间复杂度会小一些,为O(N)。
而代价是调用q(t)
时使用二分查找最大的<=t
的一个时间节点,复杂度为O(logn)
原来提示里的二分查找是用在这的啊
8、他人的智慧
惯例,来学习下他人的解法
不过,这种解法我也会就是了。果然今天的应该算是简单题吗?
9、总结
由于今天的中等题很简单,收获可能并不明显。
不过经常刷题还是得明确目标:
中等题5分钟之类出思路!
本人也在面试中被面试官出过多次算法题(字节尤其喜欢出算法题 ),个人觉得面试官出题的难度大多集中在中等题难度。
所以中等题也是我个人重点的考察对象。
最后放一张我老婆的照片。2022年怎么还不来啊!