diff --git a/146 LRU Cache.md b/146 LRU Cache.md index 34e8433..dc92fca 100644 --- a/146 LRU Cache.md +++ b/146 LRU Cache.md @@ -2,87 +2,148 @@ ## Intuition -Use a map to record each key-value pair with a timestamp(round). +The implementation uses a combination of a doubly linked list and a hash map to achieve O(1) operations. The doubly linked list maintains the order of elements (most recently used at head, least recently used at tail), while the hash map provides quick access to nodes. ## Approach -For `GET`, check the key is in our storage map or not. If not exists, return -1; otherwise, return the corresponding value and update the timestamp. +The implementation consists of these key components: -For `PUT`, also check the key is in our storage map or not. If exists, update the timestamp and return the value. -If not exists, which means we need to put the pair into the storage. Once the capacity is reached the max capacity, we need to remove the least used one, i.e, the smallest timestamp. Then we can insert the destination key-value pair. +1. Data Structures: + - `node`: Doubly linked list node containing key, value, and pointers + - `queue`: Custom doubly linked list with head and tail + - `storage`: Hash map for O(1) lookup of nodes + +2. Queue Operations: + - `insert`: Add new node at head (most recently used) + - `delete`: Remove node from tail (least recently used) + - `update`: Move existing node to head and update value + +3. Cache Operations: + - `Get`: + - If key exists: Update node position to head and return value + - If key doesn't exist: Return -1 + + - `Put`: + - If key exists: Update value and move to head + - If key doesn't exist: + - If capacity reached: Remove LRU (tail) and add new node at head + - If capacity not reached: Add new node at head ## Complexity -- Time complexity: O(N) -- Space complexity: O(N) +- Time complexity: O(1) for both Get and Put operations +- Space complexity: O(capacity) - Bounded by cache capacity ## Keywords -- LRU -- Map -- Cache +- LRU Cache +- Doubly Linked List +- Hash Map ## Code ```go type node struct { + key int val int - round int + prev *node + next *node +} + +type queue struct { + head *node + tail *node +} + +func (q *queue) insert(n *node) { + if q.head == nil { + q.head, q.tail = n, n + return + } + q.head.prev, n.next = n, q.head + q.head = n +} + +func (q *queue) delete() { + if q.head == q.tail { + q.head, q.tail = nil, nil + return + } + prev := q.tail.prev + prev.next, q.tail = nil, prev +} + +func (q *queue) update(n *node, val int) { + n.val = val + if q.head == n { + return + } + if q.tail == n { + prev := q.tail.prev + prev.next, q.tail, n.prev = nil, prev, nil + + q.head.prev, n.next = n, q.head + q.head = n + return + } + prev, next := n.prev, n.next + n.prev, n.next, prev.next, next.prev, q.head.prev = nil, q.head, next, prev, n + q.head = n } type LRUCache struct { - roundCnt int - capacity int - size int + cap int + siz int storage map[int]*node + q *queue } func Constructor(capacity int) LRUCache { return LRUCache{ - roundCnt: 0, - capacity: capacity, - size: 0, + cap: capacity, + siz: 0, storage: make(map[int]*node), + q: &queue{ + head: nil, + tail: nil, + }, } } func (l *LRUCache) Get(key int) int { - l.roundCnt += 1 - if _, ok := l.storage[key]; !ok { + var ret int + if n, find := l.storage[key]; !find { return -1 + } else { + l.q.update(n, n.val) + ret = n.val } - l.storage[key].round = l.roundCnt - return l.storage[key].val + return ret } func (l *LRUCache) Put(key int, value int) { - l.roundCnt += 1 - if _, ok := l.storage[key]; ok { - l.storage[key].val, l.storage[key].round = value, l.roundCnt - return - } - if l.size < l.capacity { - l.size += 1 - l.storage[key] = &node{ - val: value, - round: l.roundCnt, - } + if n, find := l.storage[key]; find { + l.q.update(n, value) return } - leastUsedKey, leastUsedRound:= -1, math.MaxInt - for k, v := range l.storage { - if v.round < leastUsedRound { - leastUsedKey, leastUsedRound = k, v.round - } - } - delete(l.storage, leastUsedKey) - l.storage[key] = &node{ + n := &node{ + key: key, val: value, - round: l.roundCnt, + prev: nil, + next: nil, + } + + if l.siz == l.cap { + delete(l.storage, l.q.tail.key) + l.q.delete() + l.siz -= 1 } + l.q.insert(n) + l.storage[key] = n + l.siz += 1 } diff --git a/Week1/146 AC.png b/Week1/146 AC.png new file mode 100644 index 0000000..a363db5 Binary files /dev/null and b/Week1/146 AC.png differ diff --git a/Week1/146 LRU Cache.md b/Week1/146 LRU Cache.md new file mode 100644 index 0000000..dc92fca --- /dev/null +++ b/Week1/146 LRU Cache.md @@ -0,0 +1,156 @@ +# 146. LRU Cache + +## Intuition + +The implementation uses a combination of a doubly linked list and a hash map to achieve O(1) operations. The doubly linked list maintains the order of elements (most recently used at head, least recently used at tail), while the hash map provides quick access to nodes. + +## Approach + +The implementation consists of these key components: + +1. Data Structures: + - `node`: Doubly linked list node containing key, value, and pointers + - `queue`: Custom doubly linked list with head and tail + - `storage`: Hash map for O(1) lookup of nodes + +2. Queue Operations: + - `insert`: Add new node at head (most recently used) + - `delete`: Remove node from tail (least recently used) + - `update`: Move existing node to head and update value + +3. Cache Operations: + - `Get`: + - If key exists: Update node position to head and return value + - If key doesn't exist: Return -1 + + - `Put`: + - If key exists: Update value and move to head + - If key doesn't exist: + - If capacity reached: Remove LRU (tail) and add new node at head + - If capacity not reached: Add new node at head + +## Complexity + +- Time complexity: O(1) for both Get and Put operations +- Space complexity: O(capacity) - Bounded by cache capacity + +## Keywords + +- LRU Cache +- Doubly Linked List +- Hash Map + +## Code + +```go +type node struct { + key int + val int + prev *node + next *node +} + +type queue struct { + head *node + tail *node +} + +func (q *queue) insert(n *node) { + if q.head == nil { + q.head, q.tail = n, n + return + } + q.head.prev, n.next = n, q.head + q.head = n +} + +func (q *queue) delete() { + if q.head == q.tail { + q.head, q.tail = nil, nil + return + } + prev := q.tail.prev + prev.next, q.tail = nil, prev +} + +func (q *queue) update(n *node, val int) { + n.val = val + if q.head == n { + return + } + if q.tail == n { + prev := q.tail.prev + prev.next, q.tail, n.prev = nil, prev, nil + + q.head.prev, n.next = n, q.head + q.head = n + return + } + prev, next := n.prev, n.next + n.prev, n.next, prev.next, next.prev, q.head.prev = nil, q.head, next, prev, n + q.head = n +} + +type LRUCache struct { + cap int + siz int + storage map[int]*node + q *queue +} + + +func Constructor(capacity int) LRUCache { + return LRUCache{ + cap: capacity, + siz: 0, + storage: make(map[int]*node), + q: &queue{ + head: nil, + tail: nil, + }, + } +} + + +func (l *LRUCache) Get(key int) int { + var ret int + if n, find := l.storage[key]; !find { + return -1 + } else { + l.q.update(n, n.val) + ret = n.val + } + return ret +} + + +func (l *LRUCache) Put(key int, value int) { + if n, find := l.storage[key]; find { + l.q.update(n, value) + return + } + n := &node{ + key: key, + val: value, + prev: nil, + next: nil, + } + + if l.siz == l.cap { + delete(l.storage, l.q.tail.key) + l.q.delete() + l.siz -= 1 + } + l.q.insert(n) + l.storage[key] = n + l.siz += 1 +} + + +/** + * Your LRUCache object will be instantiated and called as such: + * obj := Constructor(capacity); + * param_1 := obj.Get(key); + * obj.Put(key,value); + */ +``` diff --git a/Week1/239 AC.png b/Week1/239 AC.png new file mode 100644 index 0000000..ce2504e Binary files /dev/null and b/Week1/239 AC.png differ diff --git a/Week1/239 Sliding Window Maximum.md b/Week1/239 Sliding Window Maximum.md new file mode 100644 index 0000000..a472253 --- /dev/null +++ b/Week1/239 Sliding Window Maximum.md @@ -0,0 +1,52 @@ +# 239. Sliding Window Maximum + +## Intuition + +The key concepts of the problem is to maintain a "MAX" list. + +## Approach + +For each round, you need to remove the indexes from queue if the corresponding element in `nums` are smaller than curent number. + +After remove all the smaller elements, append the current number to the queue. + +Check the indexes in queue is in `k` range; otherwise, remove them. + +Finally, take the first index in `queue` and append the corresponding number to the return slice from `nums`. + +(`queue` will maintain a large to small list.) + +## Complexity + +- Time complexity: O(N) +- Space complexity: O(k) + +## Keywords + +- Sliding Window +- Array + +## Code + +```go +func maxSlidingWindow(nums []int, k int) []int { + queue, ret := make([]int, 0), make([]int, 0) + + for i, num := range nums { + for len(queue) > 0 && nums[queue[len(queue) - 1]] < num { + queue = queue[: len(queue) - 1] + } + queue = append(queue, i) + + if queue[0] <= i - k { + queue = queue[1:] + } + + if i >= k - 1 { + ret = append(ret, nums[queue[0]]) + } + } + + return ret +} +``` diff --git a/Week1/460 AC.png b/Week1/460 AC.png new file mode 100644 index 0000000..fd3d59f Binary files /dev/null and b/Week1/460 AC.png differ diff --git a/Week1/460 LFU Cache.md b/Week1/460 LFU Cache.md new file mode 100644 index 0000000..589fd24 --- /dev/null +++ b/Week1/460 LFU Cache.md @@ -0,0 +1,180 @@ +# 460. LFU Cache + +## Intuition + +The implementation combines a doubly linked list with hash maps to create an LFU (Least Frequently Used) cache. The main ideas are: + +1. Use a hash map `storage` to map keys to nodes +2. Use a hash map `freqMap` to map frequencies to their corresponding frequency lists +3. Each frequency list uses a doubly linked list to store nodes with the same access frequency + +## Approach + +The implementation consists of several key components: + +1. `Get` operation: + - If key doesn't exist, return -1 + - If key exists, move the node to the next frequency list and return its value + +2. `Put` operation: + - If key exists, update value and increase frequency + - If key doesn't exist: + - If capacity not reached, add to frequency-1 list + - If capacity reached, remove the first node (earliest) from the lowest frequency list, then add new node + +3. Frequency list operations: + - `freqDelete`: Remove a node from a specific frequency list + - `freqAdd`: Add a node to a specific frequency list + - `moveToNextFreq`: Move a node to the next higher frequency list + +## Complexity + +- Time complexity: O(1) +- Space complexity: O(capacity) + +## Keywords + +- LFU Cache +- Double Linked List +- Hash Map +- Frequency Counter +- Cache Implementation + +## Code + +```go +type node struct { + key int + val int + freq int + prev *node + next *node +} + +type frequence struct { + head *node + tail *node +} + +func newFreq() *frequence { + return &frequence { + head: nil, + tail: nil, + } +} + +func (f *frequence) freqDelete(n *node) { + fmt.Println(n.val) + if f.head == n { + if f.tail == n { + f.head, f.tail = nil, nil + } else { + headNext := f.head.next + headNext.prev = nil + f.head = headNext + } + n.prev, n.next = nil, nil + return + } + if f.tail == n { + tailPrev := f.tail.prev + tailPrev.next = nil + f.tail = tailPrev + n.prev, n.next = nil, nil + return + } + nPrev, nNext := n.prev, n.next + nPrev.next, nNext.prev = nNext, nPrev + n.prev, n.next = nil, nil +} + +func (f *frequence) freqAdd(n *node) { + if f.head == nil { + f.head, f.tail = n, n + n.prev, n.next = nil, nil + return + } + n.prev, n.next = f.tail, nil + f.tail.next = n + f.tail = n +} + +type LFUCache struct { + cap int + size int + storage map[int]*node + freqMap map[int]*frequence +} + + +func Constructor(capacity int) LFUCache { + l := LFUCache { + cap: capacity, + size: 0, + storage: make(map[int]*node), + freqMap: make(map[int]*frequence), + } + l.freqMap[1] = newFreq() + return l +} + +func (l *LFUCache) moveToNextFreq(n *node) { + l.freqMap[n.freq].freqDelete(n) + n.freq += 1 + if _, ok := l.freqMap[n.freq]; !ok { + l.freqMap[n.freq] = newFreq() + } + l.freqMap[n.freq].freqAdd(n) +} + +func (l *LFUCache) getLF() (int, *node) { + for i := 1; i <= len(l.freqMap); i += 1 { + if l.freqMap[i].head != nil { + return i, l.freqMap[i].head + } + } + return -1, nil +} + +func (l *LFUCache) Get(key int) int { + if _, ok := l.storage[key]; !ok { + return -1 + } + l.moveToNextFreq(l.storage[key]) + return l.storage[key].val +} + + +func (l *LFUCache) Put(key int, value int) { + if n, ok := l.storage[key]; ok { + n.val = value + l.moveToNextFreq(n) + return + } + n := node{ + key: key, + val: value, + freq: 1, + prev: nil, + next: nil, + } + l.storage[key] = &n + if l.size < l.cap { + l.freqMap[1].freqAdd(&n) + l.size += 1 + return + } + f, deleteNode := l.getLF() + l.freqMap[f].freqDelete(deleteNode) + l.freqMap[1].freqAdd(&n) + delete(l.storage, deleteNode.key) +} + + +/** + * Your LFUCache object will be instantiated and called as such: + * obj := Constructor(capacity); + * param_1 := obj.Get(key); + * obj.Put(key,value); + */ +```