Skip to content

Commit

Permalink
[chore] replace notebooks with markdown versions, update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
gitgik committed Feb 25, 2021
1 parent de58a67 commit 36a446f
Show file tree
Hide file tree
Showing 7 changed files with 450 additions and 12 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ A catalogue of data structures implementation + algorithms and coding problems a

## Linked Lists

- [Doubly linked list and all operations](linked-lists/doubly_linked_list.py)
- [Find whether linked list has loop](linked-lists/find_loop.ipynb)
- [Doubly linked list and all operations](linked-lists/doubly_linked_list.md)
- [Find whether linked list has loop](linked-lists/find_loop.md)
- [Linked list and all operations](linked-lists/linked_list.py)
- [LRU Cache implementation](linked-lists/least_recently_used_cache.ipynb)
- [Remove kth node from end](linked-lists/remove_kth_node_from_end.ipynb)
- [Reverse linked list](linked-lists/reverse_linked_list.ipynb)
- [Sort linked list in O(N log N) time and constant space](linked-lists/sorting_linked_list.ipynb)
- [LRU Cache implementation](linked-lists/least_recently_used_cache.md)
- [Remove kth node from end](linked-lists/remove_kth_node_from_end.md)
- [Reverse linked list](linked-lists/reverse_linked_list.md)
- [Sort linked list in O(N log N) time and constant space](linked-lists/sorting_linked_list.md)

## Recursion

Expand Down
146 changes: 146 additions & 0 deletions linked-lists/doubly_linked_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
```python
class Node:
def __init__(self, value):
self.value = value
self.prev = None
self.next = None


class DoubleLinkedList:
def __init__(self):
self.head = None
self.tail = None

def set_head(self, node):
"""O(1) time | O(1) space"""
if self.head is None:
self.head = node
self.tail = node
return
self.insert_before(self.head, node)

def set_tail(self, node):
"""O(1) time | O(1) space"""
if self.tail is None:
self.set_head(node)
return
self.insert_after(self.tail, node)

def insert_before(self, node, node_to_insert):
"""O(1) time | O(1) space"""
if node_to_insert == self.head and node_to_insert == self.tail:
return
# first, remove node if it exists
self.remove(node_to_insert)
node_to_insert.prev = node.prev
node_to_insert.next = node

if node.prev is None:
self.head = node_to_insert
else:
node.prev.next = node_to_insert
node.prev = node_to_insert

def insert_after(self, node, node_to_insert):
"""O(1) time | O(1) space"""
if node_to_insert == self.head and node_to_insert == self.tail:
return
# first remove the node to insert if it currently exists in the linked list
self.remove(node_to_insert)
node_to_insert.prev = node
node_to_insert.next = node.next

if node.next is None:
self.tail = node_to_insert
else:
node.prev.next = node_to_insert
node.next = node_to_insert

def insert_at_position(self, position, node_to_insert):
"""O(1) space | O(p) time - since we are iterating up until position p"""
if position == 1:
self.set_head(node_to_insert)
return
node = self.head
current_position = 1
while node is not None and current_position != position:
node = node.next
current_position += 1
if node is not None:
self.insert_before(node, node_to_insert)
else:
# node is None so we are at the tail
self.set_tail(node_to_insert)

def remove_nodes_with_value(self, value):
"""O(n) time | O(1) space"""
node = self.head
while node is not None:
# temporary save the current node, to not lose it while we check
# if it needs to be removed
node_to_remove = node
# update the node to be the next node
node = node.next
if node_to_remove.value == value:
self.remove(node_to_remove)

def remove(self, node):
"""O(1) time | O(1) space"""
if node == self.head:
self.head = self.head.next
if node == self.tail:
self.tail = self.tail.prev
self.update_node_bindings(node)

def contains_node_with_value(self, value):
"""O(n) time | O(1) space"""
node = self.head
while node is not None and node.value != value:
node = node.next
return node is not None

def update_node_bindings(self, node):
"""Update the pointers of a node when a node is removed from the doubly linked list.
"""
# the order in which you remove pointers matters. otherwise, we might delete nodes
# and never recover them forever.
if node.prev is not None:
node.prev.next = node.next
if node.next is not None:
node.next.prev = node.prev
node.prev = None
node.next = None
```


```python

doubly_linked_list = DoubleLinkedList()
first_node = Node(1)
second_node = Node(2)
third_node = Node(3)

doubly_linked_list.set_head(first_node)
doubly_linked_list.set_tail(third_node)
doubly_linked_list.insert_before(third_node, second_node)
another_node = Node(7)
doubly_linked_list.insert_after(third_node, another_node)

```


```python
doubly_linked_list.head.value
```




1




```python

```
47 changes: 47 additions & 0 deletions linked-lists/find_loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
### Looped linked list
Write a function that takes in the head of a linked list and returns the node from which the loop originates, in constant space.

Sample input:
```
1 -> 2 -> 3 -> 4
^ V
6 <- 5
```

Sample output:
```
3 -> 4
| |
6 <- 5
```


```python
class LinkedList:
def __init__(self, value):
self.value = value
self.next = None

def findLoop(head):
"""Use two pointers: Traverse the list with one iterating once, and the other jumping one node.
They eventually overlap at a point D, where the distance is equivalent to start of the list to the point where the loop is.
We can therefore obtain that loop point by taking the first pointer back to the head, then iterating both pointers one step until they converge at that point.
Time: O(n) | Space: O(1), where n = number of nodes in linked list"""
first = head.next
second = head.next.next

while first != second:
first = first.next
second = second.next.next
first = head
while first != second:
first = first.next
second = second.next
return first
```


```python

```
125 changes: 125 additions & 0 deletions linked-lists/least_recently_used_cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
## Problem
Implement a class to define an LRU(Least Recently Used) Cache. The class should allow for insertion of key-value pairs, retrieve a value using a key, and getting the most recent key.
When a key/value is inserted, the key should become the most recently used key.

- The LRUCache should store a maximum capacity (max_size) which indicates the maximum key/value pairs the cache can hold at once. It will is also be passed when the class is instantiated.

- If a key/value pair is added to the cache when the maximum capacity is reached, the cache should evict the least recently used key/value pair.

- If inserting a key/value pair that already exists in the cache, the cache should only update the value and not remove the key/value pair.

- If you attempt to retrieve a value of a key that's not in the cache should return a null value.



```python
class LRUCache:
def __init__(self, max_size):
self.cache = {}
self.current_size = 0
self.max_size = max_size or 1
self.doubly_linked_list = DoublyLinkedList()

def evict_least_recent(self):
key_to_remove = self.doubly_linked_list.tail.key
# remove tail from doubly linked list
self.doubly_linked_list.remove_tail()
# remove key from hash table pointing to the former tail we just removed
del self.cache[key_to_remove]

def get(self, key):
"""O(1) time | O(1) space."""

node = self.cache.get(key, None)
if not node:
return None
self.update_most_recent(node)
return node.value

def get_most_recent_key(self):
"""O(1) time | O(1) space."""
return self.doubly_linked_list.head.key

def insert(self, key, value):
"""O(1) time | O(1) space."""

if key not in self.cache:
# If no capacity, evict tail
if self.current_size == self.max_size:
self.evict_least_recent()
else:
self.current_size += 1
# Put new key in cache to point to new node
self.cache[key] = DoublyLinkedListNode(key, value)
else:
# Update already existing node
self.update_key(key, value)
# Update the doubly linked list
self.update_most_recent(self.cache[key])

def update_key(self, key, value):
# update existing key with new value
if key not in self.cache:
raise Exception("The provided key is not in the cache!")
self.cache[key].value = value

def update_most_recent(self, node):
self.doubly_linked_list.set_head(node)


class DoublyLinkedList:
def __init__(self):
self.head = None
self.tail = None

def set_head(self, node):
if self.head == node:
return
elif self.head is None:
self.head = node
self.tail = node
elif self.head == self.tail:
self.head.previous = node
node.next = self.head
self.head = node
else:
if self.tail == node:
self.remove_tail()
node.remove_bindings()
self.head.previous = node
node.next = self.head
self.head = node

def remove_tail(self):
if self.tail is None:
return
if self.tail == self.head:
self.head = None
self.tail = None
return

# Update new tail to be previous node, & set tail to point to Null value.
self.tail = self.tail.previous
self.tail.next = None


class DoublyLinkListNode:
def __init__(self, key, value):
self.key = key
self.value = value
self.next = None
self.previous = None

def remove_bindings(self):
if self.previous is not None:
self.previous.next = self.next
if self.next is not None:
self.next.previous = self.previous
self.previous = None
self.next = None
```


```python

```
Loading

0 comments on commit 36a446f

Please sign in to comment.