Leetcode-数据结构
q146 LRU缓存
题解
该题需要使用hash表+双向链表来解决。
我们把最常使用的节点放在链表头,而把最不常使用的节点放在链表尾,这样每次缓存容量满的时候就去掉表尾,每次访问或者添加节点的时候就将这个节点放在表头。
另外为了方便操作,我们为双向链表的两端加入了head和tail哨兵节点。
缓存中还有一个hash表,我们把key作为hash表的key,而把链表的整个节点作为hash表的value。
package main
// DLinkNode 双向链表节点
type DLinkNode struct {
key, value int
prev, next *DLinkNode
}
type LRUCache struct {
size int
cap int
cache map[int] *DLinkNode
head, tail *DLinkNode
}
// InitDLinkNode 初始化双向链表节点
func InitDLinkNode(key, value int) *DLinkNode {
return &DLinkNode{
key: key,
value: value,
}
}
func Constructor(capacity int) LRUCache {
l := LRUCache{
size: 0,
cap: capacity,
cache: make(map[int]*DLinkNode),
// 头尾两个哨兵节点置为0
head: InitDLinkNode(0, 0),
tail: InitDLinkNode(0, 0),
}
// 一开始只有这两个节点
l.head.next = l.tail
l.tail.prev = l.head
return l
}
func (this *LRUCache) Get(key int) int {
// 先判断hash表中有没有这个元素
if _, ok := this.cache[key]; !ok {
return -1
} else {
// 如果有就将它移动到链表头
node := this.cache[key]
this.moveToHead(node)
// 并返回它的key
return node.value
}
}
func (this *LRUCache) Put(key int, value int) {
// 如果hash表中没有该元素
if _, ok := this.cache[key]; !ok {
// 用传进来的key和value初始化一个节点
node := InitDLinkNode(key, value)
// 添加到hash表
this.cache[key] = node
// 添加到链表头
this.addToHead(node)
this.size++
// 查看缓存是否到达上限
if this.size > this.cap {
// 缓存到达上限就删掉尾结点
removed := this.removeTail()
delete(this.cache, removed.key)
this.size--
}
} else {
// 如果元素已经存在就更新值
node := this.cache[key]
node.value = value
// 移动到头结点的位置
this.moveToHead(node)
}
}
// 双向链表的相关操作
func (this *LRUCache) addToHead(node *DLinkNode) {
node.prev = this.head
node.next = this.head.next
this.head.next.prev = node
this.head.next = node
}
func (this *LRUCache) removeNode(node *DLinkNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
func (this *LRUCache) moveToHead(node *DLinkNode) {
this.removeNode(node)
this.addToHead(node)
}
func (this *LRUCache) removeTail() *DLinkNode {
node := this.tail.prev
this.removeNode(node)
return node
}
q208 实现Trie(前缀树)
题解
字典树是一种用于快速检索的多叉树结构。字典树把字符串看成字符序列,根据字符串中字符序列的先后顺序构造从上到下的树结构,树结构中的每一条边都对应着一个字符。字典树上存储的字符串被视为从根节点到某个节点之间的一条路径,并在终点节点上做个标记"该节点对应词语的结尾",正因为有终点节点的存在,字典树不仅可以实现简单的存储字符串,还可以实现字符串的映射,只需要将相对应的值悬挂在终点节点上即可,比如"自然"=“nature"的映射,只需要将"自然"路径上终点节点的值设置为"natrue”。
参考:https://zhuanlan.zhihu.com/p/143975546
这道题目要求构建的字典树中只包含小写字母,不是很复杂,我们首先来构建一个可以表示字典树节点的结构体:
type Trie struct {
children [26]*Trie
isTail bool
}
children字段表示一个节点后面的字符,isTail表示当前节点是否是一个单词的最后一个字符。
构造函数就是构造一个空节点:
func Constructor() Trie {
return Trie{}
}
然后是对应的插入和搜索方法:
func (t *Trie) Insert(word string) {
node := t
for _, v := range word {
v = v - 'a'
if node.children[v] == nil {
node.children[v] = &Trie{}
}
node = node.children[v]
}
node.isTail = true
}
func (t *Trie) SearchPrefix(prefix string) *Trie {
node := t
for _, v := range prefix {
v = v - 'a'
if node.children[v] == nil {
return nil
}
node = node.children[v]
}
return node
}
func (t *Trie) Search(word string) bool {
node := t.SearchPrefix(word)
return node != nil && node.isTail
}
func (t *Trie) StartsWith(prefix string) bool {
return t.SearchPrefix(prefix) != nil
}
剑指 Offer 30. 包含min函数的栈
题解
这道题其实就是最小栈,要求实现一个栈数据表结构,而且可以向外提供返回栈内最小值的API,且每个操作的时间复杂度必须为O(1)。
我们可以使用辅助栈的方法来求解,额外定义一个栈,主栈push或者pop,辅助栈也进行相应的push和pop。辅助栈在push的时候进行判断,如果当前push的值比栈内的所有元素都小,就push这个新值到栈顶,否则就push栈内的最小值到栈顶,所以我们可以时刻保证辅助栈的栈顶元素永远是栈内元素的最小值。
type MinStack struct {
stack []int
minStack []int
}
func Constructor() MinStack {
return MinStack{
stack: make([]int, 0),
minStack: []int{math.MaxInt32},
}
}
func (this *MinStack) Push(x int) {
this.stack = append(this.stack, x)
// 原来辅助栈中的最小元素
top := this.minStack[len(this.minStack)-1]
// push的时候更新辅助栈顶的最小值
this.minStack = append(this.minStack, min(top, x))
}
func (this *MinStack) Pop() {
this.stack = this.stack[:len(this.stack)-1]
this.minStack = this.minStack[:len(this.minStack)-1]
}
func (this *MinStack) Top() int {
return this.stack[len(this.stack)-1]
}
func (this *MinStack) Min() int {
// 辅助栈的栈顶就是栈内的最小值
return this.minStack[len(this.minStack)-1]
}
func min(a, b int) int {
if a < b {
return a
} else {
return b
}
}