is_in_stack
where is_in_stack[i]
tells whether .float('inf')
ormath.inf
.Sorted ➜ Bisection search
iterative DFS ➜ stack
BFS ➜ queue
Optimal substructure ➜ Dynamic programming to compute all the optimal solution of size and deduce the solution.
Examples: subarray, knapsack, longest substring
Solve an example with your brain and hands and see what “algorithm” you used.
Bottlenecks, Unnecessary work, Duplicated work
Walk through your best algorithm and look for these flaws.
Can I save time by using more space ?
Stack
l = []
, l.append()
, l.pop()
, l[i]
, len(l)
del l[i]
, l[inf:sup:step]
l.sort()
, sorted(l)
Queue
dq = deque()
, dq.popleft()
, dq.appendleft(x)
+ normal list operations except slicesDictionary / Hashtable
dic = {}
, key in dic
, dic[key]
, del dic[key]
on average, worst case Set
s = set([])
, x in s
, s.add(x)
, del s[x]
s1|s2
, s1&s2
, s1-s2
, s1^s2
Heap / Priority Queue
h = []
heappush(h, x), heappop(h)
heap = heapfy([1, 5, 2, 3...])
left = 2*i; right = left+1; father = i//2
if h[0]
in a 1-based arrayleft = 2*i+1; right = 2*i+2; father = depends on parity
otherwisedef f():
# Base case
...
# Recurisve calls
...
Bisection search ends when left
and right
cross.
def bisect(xs, key, f):
left, right = 0, len(xs)
while left < right:
mid = left + (right - left) // 2 # avoid integer overflow
if f(xs[mid], key): left = mid + 1 # Move right
else: right = mid # Move left
return left
When will they cross ?
f(x, key) = x < key
➜ Move right
when x == key
f(x, key) = x <= key
➜ Move left
when x == key
bisect_left = lambda xs,key: bisect(xs, key, lambda x,y: x<y) # First occ
bisect_right = lambda xs,key: bisect(xs, key, lambda x,y: x<=y) - 1 # Last
def merge(t, i, j, k, aux):
# merge t[i:k] in aux[i:k] assuming t[i:j] and t[j:k] are sorted
a, b = i, j # iterators
for s in range(i, k):
if a == j or (b < k and t[b] < t[a]):
aux[s] = t[b]
b += 1
else:
aux[s] = t[a]
a += 1
def mergeSort(t):
aux = [None] * len(t)
def mergeRec(i, k):
# merge sort t from i to k
if k > i + 1:
j = i + (k - i) // 2
mergeRec(i, j)
mergeRec(j, k)
merge(t, i, j, k, aux)
t[i:k] = aux[i:k]
mergeRec(0, len(t))
def countingSort(array, maxval):
"""in-place counting sort
O(n + maxval)"""
m = maxval + 1
# count the occurence of every possible value
count = [0] * m # /!\
for a in array:
count[a] += 1
i = 0
for a in range(m):
for _ in range(count[a]):
array[i] = a
i += 1
return array
- Consider adding dummy values because things like
res[x][y]
may throw index errors on corner cases.- It can be easier to find the size of the best solution and then backtrack to find the solution.
#-- 1 Naive recursive solution
def f():
# Base case
...
# Recurisve calls
...
@lru_cache(maxsize=None)
can automate the memoization process by using a dictionary to store the result of function calls (less efficient than an array).
#-- 2 Adding the memoization
res = [[-1] * len(X) for _ in range(len(Y))] # a slot for every possible parameters
# @lru_cache(maxsize=None)
def f(x, y):
# Base case
...
# Already seen case
if res[x][y] != -1:
return res[x][y]
# Recurisve calls
... # Just update the res[x][y] slot
return res[x][y]
#-- 3 Bottom up
def f(X, Y):
res = [[-1] * len(X) for _ in range(len(Y))
for x in range(1, len(X)): # skipping dummy value
for y in range(1, len(Y)):
... # update the res[x][y] slot
return res[-1][-1]
chr(65) # -> 'A'
ord('A') # -> 65
chr(97) # -> 'a'
ord('a') # -> 97
def basebTo10(x, b=2): # Horner scheme
u = 0
for a in x:
u = b * u + a
return u
def base10Tob(q, b=2):
s = ''
while q > 0:
q, r = divmod(q, b)
s = str(r) + s
return s
# when our base is a power of 2
def base10ToPowOf2(q, pow=1):
s = ''
while q > 0 or not s:
q, s = q >> pow, str(q & pow) + s
return s
#-- When there is no 0 in our base
# for instance counting excel columns
def base10TobNoZero(q, b=2):
s = ''
while q > 0 or not s:
q, r = divmod(q - 1, b) # -1 to remove 0
s = chr(65 + r) + s
return s
x << y
Returns x with the bits shifted to the left by y places (and new bits on the right-hand-side are zeros). This is the same as multiplying x by 2**y.
x >> y
Returns x with the bits shifted to the right by y places. This is the same as //'ing x by 2**y.
x & y
Does a “bitwise and”. Each bit of the output is 1 if the corresponding bit of x AND of y is 1, otherwise it’s 0. Commutative and Associative.
x | y
Does a “bitwise or”. Each bit of the output is 0 if the corresponding bit of x AND of y is 0, otherwise it’s 1. Commutative and Associative.
~ x
Returns the complement of x - the number you get by switching each 1 for a 0 and each 0 for a 1. This is the same as -x - 1.
x ^ y
Does a “bitwise exclusive or”. Each bit of the output is the same as the corresponding bit in x if that bit in y is 0, and it’s the complement of the bit in x if that bit in y is 1. Commutative and Associative.
Efficient when there is less than 64 possible values
Set | Binary |
---|---|
0 |
|
1 << i |
|
(1 << n) - 1 |
|
A | B |
|
A & B |
|
A ^ B |
|
A & B == A |
|
(1 << i) & A |
|
-A & A |
You should ideally now about:
(Most are overkill for an interview but nice to know as a software engineer)
i + 1 is either
i
+ A[i + 1]
A[i + 1]
++
if the current element is the candidate --
otherwiseb != 1
return = gcd(b, a % b)
res = res * a
if n odda = a * a
otherwiselcs[p][q]
= lcs ending at index p
in str1 and q
in str2 (DP)lcs[p][q]
= str1[p] == str2[q] ? 1 + lcs[p-1][q-1] : max(lcs[p][q-1], lcs[p-1][q])