Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 101 additions & 40 deletions 146 LRU Cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}


Expand Down
Binary file added Week1/146 AC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
156 changes: 156 additions & 0 deletions Week1/146 LRU Cache.md
Original file line number Diff line number Diff line change
@@ -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);
*/
```
Binary file added Week1/239 AC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions Week1/239 Sliding Window Maximum.md
Original file line number Diff line number Diff line change
@@ -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
}
```
Binary file added Week1/460 AC.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading