来源:力扣(LeetCode)
描述:
你需要设计一个包含验证码的验证系统。每一次验证中,用户会收到一个新的验证码,这个验证码在 currentTime
时刻之后 timeToLive
秒过期。如果验证码被更新了,那么它会在 currentTime
(可能与之前的 currentTime
不同)时刻延长 timeToLive
秒。
请你实现 AuthenticationManager
类:
AuthenticationManager(int timeToLive)
构造 AuthenticationManager
并设置 timeToLive
参数。generate(string tokenId, int currentTime)
给定 tokenId
,在当前时间 currentTime
生成一个新的验证码。renew(string tokenId, int currentTime)
将给定 tokenId
且 未过期 的验证码在 currentTime
时刻更新。如果给定 tokenId
对应的验证码不存在或已过期,请你忽略该操作,不会有任何更新操作发生。countUnexpiredTokens(int currentTime)
请返回在给定 currentTime
时刻,未过期 的验证码数目。如果一个验证码在时刻 t 过期,且另一个操作恰好在时刻 t
发生(renew
或者 countUnexpiredTokens
操作),过期事件 优先于 其他操作。
示例 1:
输入:
["AuthenticationManager", "renew", "generate", "countUnexpiredTokens", "generate", "renew", "renew", "countUnexpiredTokens"]
[[5], ["aaa", 1], ["aaa", 2], [6], ["bbb", 7], ["aaa", 8], ["bbb", 10], [15]]
输出:
[null, null, null, 1, null, null, null, 0]解释:
AuthenticationManager authenticationManager = new AuthenticationManager(5); // 构造 AuthenticationManager ,设置 timeToLive = 5 秒。
authenticationManager.renew("aaa", 1); // 时刻 1 时,没有验证码的 tokenId 为 "aaa" ,没有验证码被更新。
authenticationManager.generate("aaa", 2); // 时刻 2 时,生成一个 tokenId 为 "aaa" 的新验证码。
authenticationManager.countUnexpiredTokens(6); // 时刻 6 时,只有 tokenId 为 "aaa" 的验证码未过期,所以返回 1 。
authenticationManager.generate("bbb", 7); // 时刻 7 时,生成一个 tokenId 为 "bbb" 的新验证码。
authenticationManager.renew("aaa", 8); // tokenId 为 "aaa" 的验证码在时刻 7 过期,且 8 >= 7 ,所以时刻 8 的renew 操作被忽略,没有验证码被更新。
authenticationManager.renew("bbb", 10); // tokenId 为 "bbb" 的验证码在时刻 10 没有过期,所以 renew 操作会执行,该 token 将在时刻 15 过期。
authenticationManager.countUnexpiredTokens(15); // tokenId 为 "bbb" 的验证码在时刻 15 过期,tokenId 为 "aaa" 的验证码在时刻 7 过期,所有验证码均已过期,所以返回 0 。
提示:
方法一:哈希表
思路
按照题意,用一个哈希表 map 保存验证码和过期时间。调用 generate 时,将验证码-过期时间对直接插入 map。调用 renew 时,如果验证码存在并且未过期,则更新过期时间。调用 countUnexpiredTokens 时,遍历整个 map,统计未过期的验证码的数量。
代码:
class AuthenticationManager {
private:int timeToLive;unordered_map mp;
public:AuthenticationManager(int timeToLive) {this->timeToLive = timeToLive;}void generate(string tokenId, int currentTime) {mp[tokenId] = currentTime + timeToLive;}void renew(string tokenId, int currentTime) {if (mp.count(tokenId) && mp[tokenId] > currentTime) {mp[tokenId] = currentTime + timeToLive;}}int countUnexpiredTokens(int currentTime) {int res = 0;for (auto &[_, time] : mp) {if (time > currentTime) {res++;}}return res;}
};
执行用时:80 ms, 在所有 C++ 提交中击败了56.07%的用户
内存消耗:29.4 MB, 在所有 C++ 提交中击败了50.93%的用户
复杂度分析
时间复杂度:构造函数:O(1),generate:O(1),renew:O(1),countUnexpiredTokens:O(n),其中 n 为 generate 的调用次数。
空间复杂度:O(n),其中 n 为 generate 的调用次数,map 中有 n 个元素。
方法二:哈希表 + 双向链表
思路
用一个双向链表保存验证码和过期时间的顺序。链表的节点保存验证码和过期时间信息,并且在一条链表上,节点保存的过期时间是递增的。额外用一个哈希表 map 来保存验证码-节点对,提供根据验证码来快速访问节点的方法。调用 generate 时,新建节点,将节点插入链表末尾,并插入 map。调用 renew 时,如果验证码存在并且未过期,根据 map 访问到节点,更新过期时间,并将节点从原来位置移动到链表末尾。调用 countUnexpiredTokens 时,从链表头部开始,删除过期的节点,并从 map 删除。最后 map 的长度就是未过期的验证码的数量。
代码:
struct Node {
public:int expire;string key;Node *prev;Node *next;Node(int expire_) : expire(expire_), prev(nullptr), next(nullptr) {}Node(int expire_, string key_) : expire(expire_), key(key_), prev(nullptr), next(nullptr) {}Node(int expire_, string key_, Node *prev_, Node *next_) : expire(expire_), key(key_), prev(prev_), next(next_) {}
};class AuthenticationManager {
private:int timeToLive;Node *head;Node *tail;unordered_map mp;
public:AuthenticationManager(int timeToLive) {this->timeToLive = timeToLive;this->head = new Node(-1);this->tail = new Node(-1);this->head->next = this->tail;this->tail->prev = this->head;}void generate(string tokenId, int currentTime) {Node *node = new Node(currentTime + timeToLive, tokenId);mp[tokenId] = node;Node *last = tail->prev;last->next = node;node->prev = last;tail->prev = node;node->next = tail;}void renew(string tokenId, int currentTime) {if (mp.count(tokenId) && mp[tokenId]->expire > currentTime) {Node *node = mp[tokenId];Node *prev = node->prev;Node *next = node->next;prev->next = next;next->prev = prev;node->expire = currentTime + timeToLive;Node *last = tail->prev;last->next = node;node->prev = last;tail->prev = node;node->next = tail;}}int countUnexpiredTokens(int currentTime) {while (head->next->expire > 0 && head->next->expire <= currentTime) {Node *node = head->next;mp.erase(node->key);head->next = node->next;node->next->prev = head;delete node;}return mp.size();}
};
执行用时:68 ms, 在所有 C++ 提交中击败了87.38%的用户
内存消耗:30.1 MB, 在所有 C++ 提交中击败了7.94%的用户
复杂度分析
时间复杂度:构造函数:O(1),generate:O(1),renew:O(1),所有 countUnexpiredTokens 的调用加起来的复杂度是 O(n),其中 n 为 generate 的总调用次数。
空间复杂度:O(n),其中 n 为 generate 的调用次数,map 和链表中有 n 个元素。
author:LeetCode-Solution
上一篇:Qt 5 架构和特点
下一篇:Qt学习笔记