前言
设计一个验证码系统,给定生成的验证码和时间,主要设计精力集中在有效验证码的数据结构设计,要求存储好生成的验证码;可重置任何未过期的验证码;统计未过期的验证码数量。
一、验证码系统
二、双向链表+hash
1、idea
由于generate需求,所以需要Node数据结构存储验证码和生成时间;
由于renew会随机删除中间节点,所以采用链表存储,为了降低删除时间复杂度,结合hash;
2、go
type AuthenticationManager struct {
timeToLive int
size int
dummyHead *ListNode
dummyTail *ListNode
tokens map[string]*ListNode
}
func Constructor(timeToLive int) AuthenticationManager {
head := &ListNode{}
tail := &ListNode{}
head.Next = tail
tail.Prev = head
return AuthenticationManager{
timeToLive:timeToLive,
dummyHead:head,
dummyTail:tail,
}
}
func (this *AuthenticationManager) Generate(tokenId string, currentTime int) {
ln := &ListNode{
Val:Node{
tokenId:tokenId,
time:currentTime,
},
}
this.link(ln)
// 数据同步
this.tokens[tokenId] = ln
this.size++
}
func (this *AuthenticationManager) Renew(tokenId string, currentTime int) {
if ln,ok := this.tokens[tokenId];ok{
if ln.Val.time + this.timeToLive > currentTime {
this.unlink(ln)
this.link(ln)
}
}
}
// 清理数据
func (this *AuthenticationManager) CountUnexpiredTokens(currentTime int) int {
p := this.dummyHead.Next
for ;p != this.dummyTail; {
if p.Val.time + this.timeToLive > currentTime {
break
}
delete(this.tokens,p.Val.tokenId)
this.size--
p = p.Next
this.unlink(p.Prev)
}
return this.size
}
func(this *AuthenticationManager) link(ln *ListNode){
front := this.dummyTail.Prev
front.Next = ln;
ln.Prev = front
ln.Next = this.dummyTail
this.dummyTail.Prev = ln
}
func(this *AuthenticationManager) unlink(ln *ListNode){
front := ln.Prev
front.Next = ln.Next
ln.Next.Prev = front
}
// currentTime严格递增,所以不用优先队列排序;
// 由于renew的问题,需要将中间节点剔除放到后面保持有序,所以以队列的特点无法做到。
// 采用双向链表 + hash来做到快速删除。
type Node struct{
tokenId string
time int
}
type ListNode struct{
Val Node
Prev *ListNode
Next *ListNode
}
/**
* Your AuthenticationManager object will be instantiated and called as such:
* obj := Constructor(timeToLive);
* obj.Generate(tokenId,currentTime);
* obj.Renew(tokenId,currentTime);
* param_3 := obj.CountUnexpiredTokens(currentTime);
*/
总结
1)分析问题的过程,主要目的是存储有效时间内的验证码,联想到优先队列存储验证码和生成时间;由于currentTime的严格递增,所以自然有序,不需要优先队列,采用普通队列;由于需要重置有效时间内的任何验证码,就会涉及到中间节点的删除,这不符合队列的特点,所以转向易删除的链表,这就很像LRU思想,所以采用经典的双向链表+hash快速访问节点。
2)go的结构体字段就可以比作Java的属性,但是不像Java的class那样特别,在方法中可以直接访问变量,省略this,go的结构体方法和函数本质一样,所有需要的参数都要传递。
3)go不像Java new完,自动调用构造函数初始化,而是自己主动调用自定义方法来初始化。