Skip to content

Commit ac624b2

Browse files
committed
27/07/21 many back2backSWE questions on a range of topics
1 parent 6022731 commit ac624b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+4275
-255
lines changed

Arrays/kth_largest_element.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def simple_method(arr, k):
2929
"""
3030

3131

32-
def heap_approach(arr, k):
32+
def heap_approach_kth_largest(arr, k):
3333
q = PriorityQueue()
3434
for num in arr:
3535
q.put(num)
@@ -39,6 +39,16 @@ def heap_approach(arr, k):
3939
return q.get()
4040

4141

42+
def heap_approach_kth_smallest(arr, k):
43+
q = PriorityQueue()
44+
for num in arr:
45+
q.put(-num)
46+
if q.qsize() > k:
47+
q.get()
48+
49+
return -q.get()
50+
51+
4252
"""
4353
O(N) Approach using partitions
4454
Think of BUD (Bottlenecks, Unnecessary work, Duplicate work)
@@ -68,6 +78,8 @@ def heap_approach(arr, k):
6878

6979
def quickselect(arr, k):
7080
'''
81+
Interface
82+
----
7183
:type arr: list of int
7284
:type k: int
7385
:rtype: int
@@ -133,5 +145,5 @@ def swap(arr, first, second):
133145

134146
arr = [8, 1, 3, 2, 6, 7]
135147

136-
print(heap_approach(arr, 2))
148+
print(heap_approach_kth_smallest(arr, 2))
137149
print(quickselect(arr, 2))

Graphs/greedy_algorithms/dijkstra_algorithm_adjacency_list.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
- https://www.youtube.com/watch?v=jND_WJ8r7FE&list=PLDV1Zeh2NRsB6SWUrDFW2RmDotAfPbeHu&index=52&t=9s WilliamFiset Indexed Priority Queue
55
"""
66
import sys
7-
sys.path.append('..\\..\\Stacks and Queues')
7+
sys.path.append('..\\..\\stacksAndQueues')
88

99
from queue import PriorityQueue
1010
from math import inf

Graphs/greedy_algorithms/dijkstra_algorithm_eager_min_dary_heap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sys
2-
sys.path.append('..\\..\\Trees\\heaps')
2+
sys.path.append('..\\..\\heaps')
33

44
from math import inf
55
from dary_heap import minIndexedDHeap

Graphs/greedy_algorithms/prims_algorithm_eager_adjacency_list.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sys
2-
sys.path.append('../../Stacks and Queues')
2+
sys.path.append('..\\..\\stacksAndQueues')
33

44
from math import inf
55
from general_graph import make_complicated_graph2

Hashtables/implementLRUCache.py

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""
2+
Implement An LRU Cache
3+
An LRU cache is a cache that uses the LRU replacement policy.
4+
5+
The cache has the following api:
6+
- get(int key): Returns the value corresponding to the key key.
7+
If the key does not exist null is returned instead.
8+
- put(int key, int value): Inserts the key-value pair into the cache.
9+
If an item with key key already exists then the value will just be updated.
10+
11+
These are the special properties of the cache:
12+
- When an item is retrieved by key it moves to the conceptual "front" of the cache (it is the most recently used item)
13+
- When an item is updated it moves to the conceptual "front" of the cache (it is the most recently used item)
14+
- When a new item is inserted it is inserted to the "front"
15+
- When the cache goes over-capacity it will remove the least recently used item which will sit at the conceptual "end"
16+
of the cache to make room for the new item inserted
17+
18+
Implement an LRU cache.
19+
"""
20+
21+
22+
class LRUNode:
23+
def __init__(self, key, value, next=None, prev=None):
24+
self.key = key
25+
self.value = value
26+
self.next = next
27+
self.prev = prev
28+
29+
30+
class LRUCache:
31+
"""
32+
Approach
33+
----
34+
1. Use a linked list they are a better choice then an array because of the shifting of items (O(N)).
35+
- Linked Lists are great for removal and insertion
36+
- Even if we use a hashtable to index to items there is no way to index to the item before the tail
37+
- Doubly Linked Lists would be a better choice as we have the prev pointer. We can use a doubly linked list
38+
in combination with a hashtable to index straight into the items of interest
39+
- the keys of values would map to the memory address of the nodes
40+
41+
NOTE: HASHTABLES CAN AUGMENT MANY DATA STRUCTURES, THE INDEXED PRIORITY QUEUE IS A GREAT EXAMPLE
42+
43+
2. Time complexities should be on the order:
44+
get - O(1)
45+
put - O(1)
46+
47+
3. Model use cases
48+
insert - insert item to head of queue
49+
remove - remove from the front of the queue
50+
get - move item to front of the queue
51+
update - move item to front of the queue
52+
capacityCheck - if the cache goes over capacity we remove the end of the queue
53+
"""
54+
55+
def __init__(self):
56+
self.capacity = 0
57+
self.maxCapacity = 5
58+
# store references to nodes
59+
self.hashtable = {}
60+
self.head = None
61+
self.tail = self.head
62+
63+
def put(self, key, value):
64+
if key not in self.hashtable:
65+
# we put it at the front of the queue and place it in the hashtable
66+
newNode = LRUNode(key, value)
67+
self.hashtable[key] = newNode
68+
if self.capacity == 0:
69+
self.head = newNode
70+
self.capacity += 1
71+
else:
72+
newNode.next = self.head
73+
self.head.prev = newNode
74+
self.head = newNode
75+
# increment capacity and check
76+
self.capacity += 1
77+
self.checkCapacity()
78+
return
79+
else:
80+
node_reference = self.hashtable[key]
81+
node_reference.value = value
82+
self.moveToFront(key)
83+
return
84+
85+
def get(self, key):
86+
if key not in self.hashtable:
87+
raise KeyError(f'{key} not in LRU Cache')
88+
else:
89+
node_reference = self.hashtable[key]
90+
self.moveToFront(key)
91+
return node_reference.value
92+
93+
def moveToFront(self, key):
94+
node_reference = self.hashtable[key]
95+
# check if its already the head
96+
# we don't want to touch it then
97+
if node_reference is self.head:
98+
return
99+
# check if its the tail
100+
if node_reference is self.tail:
101+
new_tail = self.tail.prev
102+
new_tail.next = None
103+
self.tail = new_tail
104+
node_reference.next = self.head
105+
self.head.prev = node_reference
106+
node_reference.prev = None
107+
self.head = node_reference
108+
else:
109+
# pull it out
110+
node_reference.prev.next = node_reference.next
111+
node_reference.next = self.head
112+
self.head.prev = node_reference
113+
self.head = node_reference
114+
node_reference.prev = None
115+
116+
def checkCapacity(self):
117+
if self.capacity > self.maxCapacity:
118+
new_tail = self.tail.prev
119+
new_tail.next = None
120+
self.capacity -= 1
121+
return
122+
123+
124+
cache = LRUCache()
125+
cache.put('a', 3)
126+
cache.put('b', 10)
127+
cache.put('c', 3)
128+
cache.put('d', 1)
129+
cache.put('a', 1)
130+
cache.put('a', 2)
131+
cache.put('d', -10)
132+
cache.put('e', 9)
133+
134+
print(cache.get('d'))

Hashtables/maximumCollinearPoints.py

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
Maximum Collinear Points
3+
Given an array of distinct points in 2D space, find the maximum number of points that lie on the same line.
4+
5+
Example #1:
6+
Input: [[0, 1], [1, 0], [2, 0]]
7+
8+
-
9+
|
10+
-
11+
|
12+
🔵
13+
|
14+
|--|--|--|--🔵--🔵--|
15+
|
16+
-
17+
|
18+
-
19+
|
20+
-
21+
22+
Output: 2
23+
Explanation: Choose any two points; they lie on the same line. Since the three points aren’t collinear, it’s clear that we can’t do any better.
24+
25+
Example #2:
26+
Input: [[0, 1], [1, 0], [2, 0], [10, 0]]
27+
28+
-
29+
|
30+
-
31+
|
32+
🔵
33+
|
34+
|--|--|--|--🔵--🔵--|--|--|--|--|--|--|--🔵--|
35+
|
36+
-
37+
|
38+
-
39+
|
40+
-
41+
42+
Output: 3
43+
Explanation: Choose the points (1, 0), (2, 0), and (10, 0). These three points are collinear, and this is an optimal solution.
44+
"""
45+
46+
47+
class Solution:
48+
def maximumCollinearPoints(self, points):
49+
"""
50+
Interface
51+
----
52+
:type points: list of list of int
53+
:rtype: int
54+
55+
Approach
56+
----
57+
Slope Approach
58+
1. A brute force solution is to simply traverse all possible pairs of points and count the number of points that are on the
59+
line determined by the pair of points we are iterating over.
60+
61+
2. This gives a cubic-time solution. However, we can reduce our time complexity by utilizing hashing.
62+
63+
3. Instead of traversing all possible pairs of points, we traverse all possible lines.
64+
65+
4. For each point in our array, we can compute the slope of the line generated by this point and every other point in our array.
66+
67+
5.Any two points that have the same slope relative to the point that we are iterating over will lie on the same line.
68+
69+
6. Thus, we can keep a count of the number of points whose slope relative to the point that we are iterating over is equal to some value,
70+
and we can take the maximum over all such counters to determine our final answer.
71+
72+
7. To keep such a counter, one approach is to a hashtable that maps a floating-point number (representing the slope of each line) to an integer (representing
73+
a count of the number of points whose slope relative to the current point equals that value).
74+
75+
8. However, it is usually not a good idea to use floating-point numbers as keys when hashing due to potential precision issues.
76+
Thus, we instead use strings of the form "dy/dx" (where "dy" and "dx" are the change in y and x-values respectively) as our keys,
77+
and these keys map to the number of times that this slope appears relative to the point being iterated over.
78+
79+
9. We also ensure that our fraction is in reduced form by dividing both "dy" and "dx" by their greatest common divisor so that we do
80+
not end up keeping multiple counters for the same slope value.
81+
82+
Complexity
83+
----
84+
Time :
85+
Space : O(N)
86+
"""
87+
88+
# Shortcut: If we have two or less points, we can take all of them.
89+
if len(points) <= 2:
90+
return len(points)
91+
92+
answer = 0
93+
for i in range(0, len(points)):
94+
95+
# Hash Table for storing the slopes
96+
# In the form of a string
97+
slope_counter = {}
98+
99+
# Calulate the slope for each pair
100+
for j in range(i + 1, len(points)):
101+
dy = points[j][1] - points[i][1]
102+
dx = points[j][0] - points[i][0]
103+
104+
# Gcd is calculated for reducing fractions
105+
gcd = self.get_gcd(dy, dx)
106+
107+
# Make sure our fractions are fully reduced.
108+
dy //= gcd
109+
dx //= gcd
110+
111+
# Convert slopes to strings; avoid dealing with floating-point precision.
112+
signature = f'{dy}/{dx}'
113+
if signature in slope_counter:
114+
slope_counter[signature] += 1
115+
else:
116+
slope_counter[signature] = 1
117+
# Update the maximum answer
118+
for key, pointsCountForSlope in slope_counter.items():
119+
# Add 1 to include the point we iterated off of.
120+
answer = max(answer, pointsCountForSlope + 1)
121+
122+
return answer
123+
124+
# Helper Function to calculate GCD
125+
# gcd(0,n) = gcd(n,0) = n
126+
def get_gcd(self, a, b):
127+
if a == 0:
128+
return b
129+
if b == 0:
130+
return a
131+
r = a % b
132+
133+
return self.get_gcd(b, r) if r != 0 else b
134+
135+
136+
i = [[0, 1], [1, 0], [2, 0], [10, 0]]
137+
print(Solution().maximumCollinearPoints(i))

0 commit comments

Comments
 (0)