Python Algorithms¶
Problem solving with algorithms and Data structures using Python
written by sean base on following git


Github | https://github.com/newsteinking/High_pythonalgorithms
chapter 1: Sorting¶
Comparison sorts¶
These are all comparison sorts, and so cannot perform better than O(n log n) in the average or worst case.




1.1 Bubble Sort¶

Bubble sort, sometimes referred to as sinking sort, is a simple sorting algorithm that repeatedly steps through the list to be sorted, compares each pair of adjacent items and swaps them if they are in the wrong order. The pass through the list is repeated until no swaps are needed, which indicates that the list is sorted.
Properties
- Worst case performance O(n2)
- Best case performance O(n)
- Average case performance O(n2)
View the algorithm in action¶
1.1.1 Step-by-step example¶
Take an array of numbers " 5 1 4 2 8", and sort the array from lowest number to greatest number using bubble sort. In each step, elements written in bold are being compared.
Three passes will be required.
First Pass¶
( 5 1 4 2 8 ) → ( 1 5 4 2 8 ),
Here, algorithm compares the first two elements, and swaps since 5 > 1.
( 1 5 4 2 8 ) → ( 1 4 5 2 8 ), Swap since 5 > 4
( 1 4 5 2 8 ) → ( 1 4 2 5 8 ), Swap since 5 > 2
- ( 1 4 2 5 8 ) → ( 1 4 2 5 8 ),
- Now, since these elements are already in order (8 > 5), algorithm does not swap them.
Second Pass¶
( 1 4 2 5 8 ) → ( 1 4 2 5 8 )
( 1 4 2 5 8 ) → ( 1 2 4 5 8 ), Swap since 4 > 2
( 1 2 4 5 8 ) → ( 1 2 4 5 8 )
( 1 2 4 5 8 ) → ( 1 2 4 5 8 )
Now, the array is already sorted, but the algorithm does not know if it is completed. The algorithm needs one whole pass without any swap to know it is sorted.
Third Pass¶
( 1 2 4 5 8 ) → ( 1 2 4 5 8 )
( 1 2 4 5 8 ) → ( 1 2 4 5 8 )
( 1 2 4 5 8 ) → ( 1 2 4 5 8 )
( 1 2 4 5 8 ) → ( 1 2 4 5 8 )
1.1.3 Python Code¶
def bubble_sort(collection):
"""Pure implementation of bubble sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
Examples:
>>> bubble_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> bubble_sort([])
[]
>>> bubble_sort([-2, -5, -45])
[-45, -5, -2]
>>> bubble_sort([-23,0,6,-4,34])
[-23,-4,0,6,34]
"""
length = len(collection)
for i in range(length-1):
swapped = False
for j in range(length-1-i):
if collection[j] > collection[j+1]:
swapped = True
collection[j], collection[j+1] = collection[j+1], collection[j]
if not swapped: break # Stop iteration if the collection is sorted.
return collection
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:').strip()
unsorted = [int(item) for item in user_input.split(',')]
print(*bubble_sort(unsorted), sep=',')
1.1.4 Bubble Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 20
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Bubble Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
class sort():
def __init__(self,arr):
self.arr = arr
self.n = len(arr)
self.i = 1
self.image = pygame.Surface((width - width/5,height - height/5))
self.rect = self.image.get_rect()
self.rect.left = width/10
self.rect.top = height/10
self.width_per_bar = self.rect.width / self.n - 2
def update(self):
if self.i < self.n:
self.image.fill(black)
#################Sorting Algorithm here#############################
for j in range(0,self.n - self.i):
if self.arr[j] > self.arr[j+1]:
self.arr[j],self.arr[j+1] = self.arr[j+1],self.arr[j]
self.i += 1
####################################################################
l = 0
for k in range(0,int(self.rect.width),int(self.width_per_bar + 2)):
bar = pygame.Surface((self.width_per_bar,self.arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = self.rect.height
bar_rect.left = k
self.image.blit(bar,bar_rect)
l += 1
else:
pass
def draw(self):
screen.blit(self.image,self.rect)
def main():
arr = generatearray(1,height - height/5 - 10,240)
bubble_sort = sort(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
bubble_sort.update()
screen.fill(black)
print(bubble_sort.arr)
bubble_sort.draw()
pygame.display.update()
clock.tick(FPS)
main()
1.2 Selection Sort¶

Selection sort is an algorithm that divides the input list into two parts: the sublist of items already sorted, which is built up from left to right at the front (left) of the list, and the sublist of items remaining to be sorted that occupy the rest of the list. Initially, the sorted sublist is empty and the unsorted sublist is the entire input list. The algorithm proceeds by finding the smallest (or largest, depending on sorting order) element in the unsorted sublist, exchanging (swapping) it with the leftmost unsorted element (putting it in sorted order), and moving the sublist boundaries one element to the right.
Properties
- Worst case performance O(n2)
- Best case performance O(n2)
- Average case performance O(n2)
View the algorithm in action¶
1.2.2 Python Code¶
This is a pure python implementation of the selection sort algorithm
For doctests run following command:
python -m doctest -v selection_sort.py
or
python3 -m doctest -v selection_sort.py
For manual testing run:
python selection_sort.py
from __future__ import print_function
def selection_sort(collection):
"""Pure implementation of the selection sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
Examples:
>>> selection_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> selection_sort([])
[]
>>> selection_sort([-2, -5, -45])
[-45, -5, -2]
"""
length = len(collection)
for i in range(length - 1):
least = i
for k in range(i + 1, length):
if collection[k] < collection[least]:
least = k
collection[least], collection[i] = (
collection[i], collection[least]
)
return collection
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:\n').strip()
unsorted = [int(item) for item in user_input.split(',')]
print(selection_sort(unsorted))
1.2.3 Selection Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 20
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Selection Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
class sort():
def __init__(self,arr):
self.arr = arr
self.n = len(arr)
self.i = 0
self.image = pygame.Surface((width - width/5,height - height/5))
self.rect = self.image.get_rect()
self.rect.left = width/10
self.rect.top = height/10
self.width_per_bar = self.rect.width / self.n - 2
def update(self):
if self.i < self.n:
self.image.fill(black)
#################Sorting Algorithm here#############################
small_index = self.i
for j in range(self.i,self.n):
if self.arr[j] < self.arr[small_index]:
small_index = j
self.arr[small_index],self.arr[self.i] = self.arr[self.i],self.arr[small_index]
self.i += 1
####################################################################
l = 0
for k in range(0,int(self.rect.width),int(self.width_per_bar + 2)):
bar = pygame.Surface((self.width_per_bar,self.arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = self.rect.height
bar_rect.left = k
self.image.blit(bar,bar_rect)
l += 1
else:
pass
def draw(self):
screen.blit(self.image,self.rect)
def main():
arr = generatearray(1,height - height/5 - 10,240)
selection_sort = sort(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
selection_sort.update()
screen.fill(black)
print(selection_sort.arr)
selection_sort.draw()
pygame.display.update()
clock.tick(FPS)
main()
1.3 Insertion Sort¶

Insertion sort is a simple sorting algorithm that builds the final sorted array (or list) one item at a time. It is much less efficient on large lists than more advanced algorithms such as quicksort, heapsort, or merge sort.
Properties
- Worst case performance O(n2)
- Best case performance O(n)
- Average case performance O(n2)
View the algorithm in action¶
1.3.2 Python Code¶
def insertion_sort(collection):
"""Pure implementation of the insertion sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
Examples:
>>> insertion_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> insertion_sort([])
[]
>>> insertion_sort([-2, -5, -45])
[-45, -5, -2]
"""
for index in range(1, len(collection)):
while index > 0 and collection[index - 1] > collection[index]:
collection[index], collection[index - 1] = collection[index - 1], collection[index]
index -= 1
return collection
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:\n').strip()
unsorted = [int(item) for item in user_input.split(',')]
print(insertion_sort(unsorted))
1.3.3 Inertion Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 20
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Insertion Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
class sort():
def __init__(self,arr):
self.arr = arr
self.n = len(arr)
self.i = 2
self.image = pygame.Surface((width - width/5,height - height/5))
self.rect = self.image.get_rect()
self.rect.left = width/10
self.rect.top = height/10
self.width_per_bar = self.rect.width / self.n - 2
def update(self):
if self.i < self.n:
self.image.fill(black)
#################Sorting Algorithm here#############################
for j in range(self.i,0,-1):
if self.arr[j] < self.arr[j-1]:
self.arr[j],self.arr[j - 1] = self.arr[j - 1],self.arr[j]
self.i += 1
####################################################################
l = 0
for k in range(0,int(self.rect.width),int(self.width_per_bar + 2)):
bar = pygame.Surface((self.width_per_bar,self.arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = self.rect.height
bar_rect.left = k
self.image.blit(bar,bar_rect)
l += 1
else:
pass
def draw(self):
screen.blit(self.image,self.rect)
def main():
arr = generatearray(1,height - height/5 - 10,240)
insertion_sort = sort(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
insertion_sort.update()
screen.fill(black)
print(insertion_sort.arr)
insertion_sort.draw()
pygame.display.update()
clock.tick(FPS)
main()
1.4 Merge Sort¶

Merge sort (also commonly spelled mergesort) is an efficient, general-purpose, comparison-based sorting algorithm. Most implementations produce a stable sort, which means that the implementation preserves the input order of equal elements in the sorted output. Mergesort is a divide and conquer algorithm that was invented by John von Neumann in 1945.
Properties
- Worst case performance O(n log n)
- Best case performance O(n log n)
- Average case performance O(n log n)
View the algorithm in action¶
1.4.2 Python Code¶
def merge_sort(collection):
"""Pure implementation of the merge sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
Examples:
>>> merge_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> merge_sort([])
[]
>>> merge_sort([-2, -5, -45])
[-45, -5, -2]
"""
length = len(collection)
if length > 1:
midpoint = length // 2
left_half = merge_sort(collection[:midpoint])
right_half = merge_sort(collection[midpoint:])
i = 0
j = 0
k = 0
left_length = len(left_half)
right_length = len(right_half)
while i < left_length and j < right_length:
if left_half[i] < right_half[j]:
collection[k] = left_half[i]
i += 1
else:
collection[k] = right_half[j]
j += 1
k += 1
while i < left_length:
collection[k] = left_half[i]
i += 1
k += 1
while j < right_length:
collection[k] = right_half[j]
j += 1
k += 1
return collection
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:\n').strip()
unsorted = [int(item) for item in user_input.split(',')]
print(merge_sort(unsorted))
#===========================================================================
Python implementation of merge sort algorithm.
Takes an average of 0.6 microseconds to sort a list of length 1000 items.
Best Case Scenario : O(n)
Worst Case Scenario : O(n)
def merge_sort(LIST):
start = []
end = []
while len(LIST) > 1:
a = min(LIST)
b = max(LIST)
start.append(a)
end.append(b)
LIST.remove(a)
LIST.remove(b)
if LIST: start.append(LIST[0])
end.reverse()
return (start + end)
1.4.3 Merge Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 40
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Merge Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
def mergesort(arr,temparr,left,right):
if left < right:
mid = int((left + right)/2)
mergesort(arr,temparr,left,mid)
mergesort(arr,temparr,mid+1,right)
merge(arr,temparr,left,mid + 1,right)
else:
pass
def merge(arr,temp,left,mid,right):
left_end = mid - 1
temp_pos = left
size = right - left + 1
while left <= left_end and mid<=right:
if arr[left] <= arr[mid]:
temp[temp_pos] = arr[left]
temp_pos = temp_pos + 1
left = left + 1
else:
temp[temp_pos] = arr[mid]
temp_pos = temp_pos + 1
mid = mid + 1
while left<=left_end:
temp[temp_pos] = arr[left]
left = left + 1
temp_pos = temp_pos + 1
while mid <= right:
temp[temp_pos] = arr[mid]
mid = mid + 1
temp_pos = temp_pos + 1
for i in range(0,size):
arr[right] = temp[right]
right = right - 1
displayarray(arr)
def displayarray(arr):
image = pygame.Surface((width - width/5,height - height/5))
rect = image.get_rect()
rect.top = height/10
rect.left = width/10
width_per_bar = rect.width/len(arr) - 2
l = 0
for k in range(0,int(rect.width),int(width_per_bar + 2)):
bar = pygame.Surface((width_per_bar,arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = rect.height
bar_rect.left = k
image.blit(bar,bar_rect)
l += 1
screen.fill(black)
screen.blit(image,rect)
pygame.display.update()
clock.tick(FPS)
def main():
arr = generatearray(1,height - height/5 - 10,240)
temparr = [0]*len(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
if sorted(arr) != arr:
mergesort(arr,temparr,0,len(arr) - 1)
else:
displayarray(arr)
main()
1.5 Quick Sort¶

Quicksort (sometimes called partition-exchange sort) is an efficient sorting algorithm, serving as a systematic method for placing the elements of an array in order.
Properties
- Worst case performance O(n2)
- Best case performance O(n log n) or O(n) with three-way partition
- Average case performance O(n log n)
View the algorithm in action¶
1.5.2 Python Code¶
This is a pure python implementation of the quick sort algorithm
For doctests run following command:
python -m doctest -v quick_sort.py
or
python3 -m doctest -v quick_sort.py
For manual testing run:
python quick_sort.py
from __future__ import print_function
def quick_sort(ARRAY):
"""Pure implementation of quick sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
Examples:
>>> quick_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> quick_sort([])
[]
>>> quick_sort([-2, -5, -45])
[-45, -5, -2]
"""
ARRAY_LENGTH = len(ARRAY)
if( ARRAY_LENGTH <= 1):
return ARRAY
else:
PIVOT = ARRAY[0]
GREATER = [ element for element in ARRAY[1:] if element > PIVOT ]
LESSER = [ element for element in ARRAY[1:] if element <= PIVOT ]
return quick_sort(LESSER) + [PIVOT] + quick_sort(GREATER)
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:\n').strip()
unsorted = [ int(item) for item in user_input.split(',') ]
print( quick_sort(unsorted) )
#=======================================================================
from __future__ import print_function
def quick_sort_3partition(sorting, left, right):
if right <= left:
return
a = i = left
b = right
pivot = sorting[left]
while i <= b:
if sorting[i] < pivot:
sorting[a], sorting[i] = sorting[i], sorting[a]
a += 1
i += 1
elif sorting[i] > pivot:
sorting[b], sorting[i] = sorting[i], sorting[b]
b -= 1
else:
i += 1
quick_sort_3partition(sorting, left, a - 1)
quick_sort_3partition(sorting, b + 1, right)
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:\n').strip()
unsorted = [ int(item) for item in user_input.split(',') ]
quick_sort_3partition(unsorted,0,len(unsorted)-1)
print(unsorted)
1.5.3 Quick Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 40
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Quick Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
def partition(arr,low,high):
i = low-1
pivot = arr[high]
for j in range(low , high):
if arr[j] <= pivot:
i = i+1
arr[i],arr[j] = arr[j],arr[i]
displayarray(arr)
arr[i+1],arr[high] = arr[high],arr[i+1]
return i+1
def quicksort(arr,low,high):
if low < high:
pi = partition(arr,low,high)
quicksort(arr, low, pi-1)
quicksort(arr, pi+1, high)
def displayarray(arr):
image = pygame.Surface((width - width/5,height - height/5))
rect = image.get_rect()
rect.top = height/10
rect.left = width/10
width_per_bar = rect.width/len(arr) - 2
l = 0
for k in range(0,int(rect.width),int(width_per_bar + 2)):
bar = pygame.Surface((width_per_bar,arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = rect.height
bar_rect.left = k
image.blit(bar,bar_rect)
l += 1
screen.fill(black)
screen.blit(image,rect)
pygame.display.update()
clock.tick(FPS)
def main():
arr = generatearray(1,height - height/5 - 10,240)
temparr = [0]*len(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
if sorted(arr) != arr:
quicksort(arr,0,len(arr) - 1)
else:
displayarray(arr)
main()
1.6 Heap Sort¶
Heapsort is a comparison-based sorting algorithm. It can be thought of as an improved selection sort. It divides its input into a sorted and an unsorted region, and it iteratively shrinks the unsorted region by extracting the largest element and moving that to the sorted region.
Properties
- Worst case performance O(n log n)
- Best case performance O(n log n)
- Average case performance O(n log n)
View the algorithm in action¶
1.6.1 Heap sort Animation¶

1.6.2 Python Code¶
This is a pure python implementation of the heap sort algorithm.
For doctests run following command:
python -m doctest -v heap_sort.py
or
python3 -m doctest -v heap_sort.py
For manual testing run:
python heap_sort.py
from __future__ import print_function
def heapify(unsorted, index, heap_size):
largest = index
left_index = 2 * index + 1
right_index = 2 * index + 2
if left_index < heap_size and unsorted[left_index] > unsorted[largest]:
largest = left_index
if right_index < heap_size and unsorted[right_index] > unsorted[largest]:
largest = right_index
if largest != index:
unsorted[largest], unsorted[index] = unsorted[index], unsorted[largest]
heapify(unsorted, largest, heap_size)
def heap_sort(unsorted):
'''
Pure implementation of the heap sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
Examples:
>>> heap_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> heap_sort([])
[]
>>> heap_sort([-2, -5, -45])
[-45, -5, -2]
'''
n = len(unsorted)
for i in range(n // 2 - 1, -1, -1):
heapify(unsorted, i, n)
for i in range(n - 1, 0, -1):
unsorted[0], unsorted[i] = unsorted[i], unsorted[0]
heapify(unsorted, 0, i)
return unsorted
if __name__ == '__main__':
#===========================================================================
# try:
# raw_input # Python 2
# except NameError:
# raw_input = input # Python 3
#===========================================================================
user_input = input('Enter numbers separated by a comma:\n').strip()
unsorted = [int(item) for item in user_input.split(',')]
print(heap_sort(unsorted))
1.6.3 Heap Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 80
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Heap Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i]
heapify(arr, n, largest)
displayarray(arr)
def heapSort(arr):
n = len(arr)
for i in range(n, -1, -1):
heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
def displayarray(arr):
image = pygame.Surface((width - width/5,height - height/5))
rect = image.get_rect()
rect.top = height/10
rect.left = width/10
width_per_bar = rect.width/len(arr) - 2
l = 0
for k in range(0,int(rect.width),int(width_per_bar + 2)):
bar = pygame.Surface((width_per_bar,arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = rect.height
bar_rect.left = k
image.blit(bar,bar_rect)
l += 1
screen.fill(black)
screen.blit(image,rect)
pygame.display.update()
clock.tick(FPS)
def main():
arr = generatearray(1,height - height/5 - 10,240)
temparr = [0]*len(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
if sorted(arr) != arr:
heapSort(arr)
else:
displayarray(arr)
main()
1.7 Randix Sort¶
From Wikipedia: Radix sort is a non-comparative integer sorting algorithm that sorts data with integer keys by grouping keys by the individual digits which share the same significant position and value.
Properties
- Worst case performance O(wn)
- Best case performance O(wn)
- Average case performance O(wn)
Source: Wikipedia¶
1.7.2 Python Code¶
def radixsort(lst):
RADIX = 10
maxLength = False
tmp , placement = -1, 1
while not maxLength:
maxLength = True
# declare and initialize buckets
buckets = [list() for _ in range( RADIX )]
# split lst between lists
for i in lst:
tmp = int((i / placement) % RADIX)
buckets[tmp].append(i)
if maxLength and tmp > 0:
maxLength = False
# empty lists into lst array
a = 0
for b in range( RADIX ):
buck = buckets[b]
for i in buck:
lst[a] = i
a += 1
# move to next
placement *= RADIX
1.7.3 Randix Sort Animation¶
import random
import pygame
from pygame.locals import *
scr_size = (width,height) = (900,600)
FPS = 40
screen = pygame.display.set_mode(scr_size)
clock = pygame.time.Clock()
black = (0,0,0)
white = (255,255,255)
pygame.display.set_caption('Radix Sort')
def generatearray(lowerlimit,upperlimit,length):
arr = []
for i in range(0,length):
arr.append(2*i)
#arr.append(random.randrange(lowerlimit,upperlimit))
random.shuffle(arr)
return arr
# arr = []
# for i in range(0,length):
# arr.append(random.randrange(lowerlimit,upperlimit))
#
# return arr
def countingSort(arr, exp1):
n = len(arr)
output = [0] * (n)
count = [0] * (10)
for i in range(0, n):
index = (arr[i]/exp1)
count[ int((index)%10) ] += 1
for i in range(1,10):
count[i] += count[i-1]
i = n-1
while i>=0:
index = (arr[i]/exp1)
output[ count[ int((index)%10) ] - 1] = arr[i]
count[ int((index)%10) ] -= 1
i -= 1
i = 0
for i in range(0,len(arr)):
arr[i] = output[i]
displayarray(arr)
def radixSort(arr):
max1 = max(arr)
exp = 1
while max1/exp > 0:
countingSort(arr,exp)
exp *= 10
def displayarray(arr):
image = pygame.Surface((width - width/5,height - height/5))
rect = image.get_rect()
rect.top = height/10
rect.left = width/10
width_per_bar = rect.width/len(arr) - 2
l = 0
for k in range(0,int(rect.width),int(width_per_bar + 2)):
bar = pygame.Surface((width_per_bar,arr[l]))
bar_rect = bar.get_rect()
bar.fill(white)
bar_rect.bottom = rect.height
bar_rect.left = k
image.blit(bar,bar_rect)
l += 1
screen.fill(black)
screen.blit(image,rect)
pygame.display.update()
clock.tick(FPS)
def main():
arr = generatearray(1,height - height/5 - 10,240)
temparr = [0]*len(arr)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
if event.type == pygame.KEYDOWN:
pass
if event.type == pygame.KEYUP:
pass
if sorted(arr) != arr:
radixSort(arr)
else:
displayarray(arr)
main()
1.8 Cocktail shaker sort¶

Cocktail shaker sort, also known as bidirectional bubble sort, cocktail sort, shaker sort (which can also refer to a variant of selection sort), ripple sort, shuffle sort, or shuttle sort, is a variation of bubble sort that is both a stable sorting algorithm and a comparison sort. The algorithm differs from a bubble sort in that it sorts in both directions on each pass through the list.
Properties
- Worst case performance O(n2)
- Best case performance O(n)
- Average case performance O(n2)
Source: Wikipedia¶
1.8.1 Cocktail shaker sort Animation¶

1.8.2 Python Code¶
from __future__ import print_function
def cocktail_shaker_sort(unsorted):
"""
Pure implementation of the cocktail shaker sort algorithm in Python.
"""
for i in range(len(unsorted)-1, 0, -1):
swapped = False
for j in range(i, 0, -1):
if unsorted[j] < unsorted[j-1]:
unsorted[j], unsorted[j-1] = unsorted[j-1], unsorted[j]
swapped = True
for j in range(i):
if unsorted[j] > unsorted[j+1]:
unsorted[j], unsorted[j+1] = unsorted[j+1], unsorted[j]
swapped = True
if not swapped:
return unsorted
if __name__ == '__main__':
try:
raw_input # Python 2
except NameError:
raw_input = input # Python 3
user_input = raw_input('Enter numbers separated by a comma:\n').strip()
unsorted = [int(item) for item in user_input.split(',')]
cocktail_shaker_sort(unsorted)
print(unsorted)
1.9 Shell sort¶

Shellsort is a generalization of insertion sort that allows the exchange of items that are far apart. The idea is to arrange the list of elements so that, starting anywhere, considering every nth element gives a sorted list. Such a list is said to be h-sorted. Equivalently, it can be thought of as h interleaved lists, each individually sorted.
Properties
- Worst case performance O(nlog2n)
- Best case performance O(n log n)
- Average case performance depends on gap sequence
View the algorithm in action¶
1.9.1 Shell sort Animation¶

The step-by-step process of replacing pairs of items during the shell sorting algorithm.

1.9.2 Python Code¶
This is a pure python implementation of the shell sort algorithm
For doctests run following command:
python -m doctest -v shell_sort.py
or
python3 -m doctest -v shell_sort.py
For manual testing run:
python shell_sort.py
from __future__ import print_function
def shell_sort(collection):
"""Pure implementation of shell sort algorithm in Python
:param collection: Some mutable ordered collection with heterogeneous
comparable items inside
:return: the same collection ordered by ascending
>>> shell_sort([0, 5, 3, 2, 2])
[0, 2, 2, 3, 5]
>>> shell_sort([])
[]
>>> shell_sort([-2, -5, -45])
[-45, -5, -2]
"""
# Marcin Ciura's gap sequence
gaps = [701, 301, 132, 57, 23, 10, 4, 1]
for gap in gaps:
i = gap
while i < len(collection):
temp = collection[i]
j = i
while j >= gap and collection[j - gap] > temp:
collection[j] = collection[j - gap]
j -= gap
collection[j] = temp
i += 1
return collection
if __name__ == '__main__':
try:
raw_input # Python 2
except NameError:
raw_input = input # Python 3
user_input = raw_input('Enter numbers separated by a comma:\n').strip()
unsorted = [int(item) for item in user_input.split(',')]
print(shell_sort(unsorted))
Non Comparison sorts¶
The following table describes integer sorting algorithms and other sorting algorithms that are not comparison sorts. As such, they are not limited to Ω(n log n) . Complexities below assume n items to be sorted, with keys of size k, digit size d, and r the range of numbers to be sorted. Many of them are based on the assumption that the key size is large enough that all entries have unique key values, and hence that n ≪ 2k, where ≪ means "much less than". In the unit-cost random access machine model, algorithms with running time of {displaystyle scriptstyle ncdot {frac {k}{d}}} {displaystyle scriptstyle ncdot {frac {k}{d}}}, such as radix sort, still take time proportional to Θ(n log n), because n is limited to be not more than {displaystyle 2^{frac {k}{d}}} 2^{frac {k}{d}}, and a larger number of elements to sort would require a bigger k in order to store them in the memory.


1.10 Bucket Sort¶


Bucket sort, or bin sort, is a sorting algorithm that works by distributing the elements of an array into a number of buckets. Each bucket is then sorted individually, either using a different sorting algorithm, or by recursively applying the bucket sorting algorithm.
Properties
- Worst case performance O(n2)
- Best case performance O(n+k)
- Average case performance O(n+k)


from __future__ import print_function
from insertion_sort import insertion_sort
import math
DEFAULT_BUCKET_SIZE = 5
def bucketSort(myList, bucketSize=DEFAULT_BUCKET_SIZE):
if(len(myList) == 0):
print('You don\'t have any elements in array!')
minValue = myList[0]
maxValue = myList[0]
# For finding minimum and maximum values
for i in range(0, len(myList)):
if myList[i] < minValue:
minValue = myList[i]
elif myList[i] > maxValue:
maxValue = myList[i]
# Initialize buckets
bucketCount = math.floor((maxValue - minValue) / bucketSize) + 1
buckets = []
for i in range(0, bucketCount):
buckets.append([])
# For putting values in buckets
for i in range(0, len(myList)):
buckets[math.floor((myList[i] - minValue) / bucketSize)].append(myList[i])
# Sort buckets and place back into input array
sortedArray = []
for i in range(0, len(buckets)):
insertion_sort(buckets[i])
for j in range(0, len(buckets[i])):
sortedArray.append(buckets[i][j])
return sortedArray
if __name__ == '__main__':
sortedArray = bucketSort([12, 23, 4, 5, 3, 2, 12, 81, 56, 95])
print(sortedArray)
chapter 2: Arrays¶
2.1 delete nth¶
Given a list lst and a number N, create a new list that contains each number of the list at most N times without reordering.
For example if N = 2, and the input is [1,2,3,1,2,1,2,3], you take [1,2,3,1,2], drop the next [1,2] since this would lead to 1 and 2 being in the result 3 times, and then take 3, which leads to [1,2,3,1,2,3]
import collections
# Time complexity O(n^2)
def delete_nth_naive(array, n):
ans = []
for num in array:
if ans.count(num) < n:
ans.append(num)
return ans
# Time Complexity O(n), using hash tables.
def delete_nth(array, n):
result = []
counts = collections.defaultdict(int) # keep track of occurrences
for i in array:
if counts[i] < n:
result.append(i)
counts[i] += 1
return result
2.2 flatten¶
Implement Flatten Arrays. Given an array that may contain nested arrays, produce a single resultant array.
from collections import Iterable
# return list
def flatten(input_arr, output_arr=None):
if output_arr is None:
output_arr = []
for ele in input_arr:
if isinstance(ele, Iterable):
flatten(ele, output_arr) #tail-recursion
else:
output_arr.append(ele) #produce the result
return output_arr
# returns iterator
def flatten_iter(iterable):
"""
Takes as input multi dimensional iterable and
returns generator which produces one dimensional output.
"""
for element in iterable:
if isinstance(element, Iterable):
yield from flatten_iter(element)
else:
yield element
2.3 garage¶
There is a parking lot with only one empty spot. Given the initial state of the parking lot and the final state. Each step we are only allowed to move a car out of its place and move it into the empty spot. The goal is to find out the least movement needed to rearrange the parking lot from the initial state to the final state.
Say the initial state is an array:
[1, 2, 3, 0, 4], where 1, 2, 3, 4 are different cars, and 0 is the empty spot.
And the final state is
[0, 3, 2, 1, 4]. We can swap 1 with 0 in the initial array to get [0, 2, 3, 1, 4] and so on. Each step swap with 0 only.
Edit: Now also prints the sequence of changes in states. Output of this example :-
initial: [1, 2, 3, 0, 4] final: [0, 3, 2, 1, 4] Steps = 4 Sequence : 0 2 3 1 4 2 0 3 1 4 2 3 0 1 4 0 3 2 1 4
def garage(initial, final):
initial = initial[::] # prevent changes in original 'initial'
seq = [] # list of each step in sequence
steps = 0
while initial != final:
zero = initial.index(0)
if zero != final.index(0): # if zero isn't where it should be,
car_to_move = final[zero] # what should be where zero is,
pos = initial.index(car_to_move) # and where is it?
initial[zero], initial[pos] = initial[pos], initial[zero]
else:
for i in range(len(initial)):
if initial[i] != final[i]:
initial[zero], initial[i] = initial[i], initial[zero]
break
seq.append(initial[::])
steps += 1
return steps, seq
# e.g.: 4, [{0, 2, 3, 1, 4}, {2, 0, 3, 1, 4},
# {2, 3, 0, 1, 4}, {0, 3, 2, 1, 4}]
"""
thus:
1 2 3 0 4 -- zero = 3, true, car_to_move = final[3] = 1,
pos = initial.index(1) = 0, switched [0], [3]
0 2 3 1 4 -- zero = 0, f, initial[1] != final[1], switched 0,1
2 0 3 1 4 -- zero = 1, t, car_to_move = final[1] = 3,
pos = initial.index(3) = 2, switched [1], [2]
2 3 0 1 4 -- zero = 2, t, car_to_move = final[2] = 2,
pos = initial.index(2) = 0, switched [0], [2]
0 3 2 1 4 -- initial == final
"""
2.4 josephus¶
There are people sitting in a circular fashion, print every third member while removing them, the next counter starts immediately after the member is removed. Print till all the members are exhausted.
For example: Input: consider 123456789 members sitting in a circular fashion, Output: 369485271
def josephus(int_list, skip):
skip = skip - 1 # list starts with 0 index
idx = 0
len_list = (len(int_list))
while len_list > 0:
idx = (skip + idx) % len_list # hash index to every 3rd
yield int_list.pop(idx)
len_list -= 1
2.5 limit¶
- Sometimes you need to limit array result to use. Such as you only need the
- value over 10 or, you need value under than 100. By use this algorithms, you can limit your array to specific value
- If array, Min, Max value was given, it returns array that contains values of
- given array which was larger than Min, and lower than Max. You need to give 'unlimit' to use only Min or Max.
ex) limit([1,2,3,4,5], None, 3) = [1,2,3]
Complexity = O(n)
# tl:dr -- array slicing by value
def limit(arr, min_lim = None, max_lim = None):
result = []
if min_lim == None:
for i in arr:
if i <= max_lim:
result.append(i)
elif max_lim == None:
for i in arr:
if i >= min_lim:
result.append(i)
else:
for i in arr:
if i >= min_lim and i <= max_lim:
result.append(i)
return result
2.6 longest non repeat¶
Given a string, find the length of the longest substring without repeating characters.
Examples: Given "abcabcbb", the answer is "abc", which the length is 3. Given "bbbbb", the answer is "b", with the length of 1. Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring. """
def longest_non_repeat_v1(string):
"""
Find the length of the longest substring
without repeating characters.
"""
dict = {}
max_length = 0
j = 0
for i in range(len(string)):
if string[i] in dict:
j = max(dict[string[i]], j)
dict[string[i]] = i + 1
max_length = max(max_length, i - j + 1)
return max_length
def longest_non_repeat_v2(string):
"""
Find the length of the longest substring
without repeating characters.
Uses alternative algorithm.
"""
if string is None:
return 0
start, max_len = 0, 0
used_char = {}
for index, char in enumerate(string):
if char in used_char and start <= used_char[char]:
start = used_char[char] + 1
else:
max_len = max(max_len, index - start + 1)
used_char[char] = index
return max_len
# get functions of above, returning the max_len and substring
def get_longest_non_repeat_v1(string):
"""
Find the length of the longest substring
without repeating characters.
Return max_len and the substring as a tuple
"""
if string is None:
return 0
temp = []
max_len = 0
for i in string:
if i in temp:
temp = []
temp.append(i)
max_len = max(max_len, len(temp))
return max_len, temp
def get_longest_non_repeat_v2(string):
"""
Find the length of the longest substring
without repeating characters.
Uses alternative algorithm.
Return max_len and the substring as a tuple
"""
if string is None:
return 0
start, max_len = 0, 0
used_char = {}
for index, char in enumerate(string):
if char in used_char and start <= used_char[char]:
start = used_char[char] + 1
else:
max_len = max(max_len, index - start + 1)
used_char[char] = index
return max_len, start
2.7 Max ones index¶
Find the index of 0 to be replaced with 1 to get longest continuous sequence of 1s in a binary array. Returns index of 0 to be replaced with 1 to get longest continuous sequence of 1s. If there is no 0 in array, then it returns -1.
e.g. let input array = [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1] If we replace 0 at index 3 with 1, we get the longest continuous sequence of 1s in the array. So the function return => 3
def max_ones_index(arr):
n = len(arr)
max_count = 0
max_index = 0
prev_zero = -1
prev_prev_zero = -1
for curr in range(n):
# If current element is 0,
# then calculate the difference
# between curr and prev_prev_zero
if arr[curr] == 0:
if curr - prev_prev_zero > max_count:
max_count = curr - prev_prev_zero
max_index = prev_zero
prev_prev_zero = prev_zero
prev_zero = curr
if n - prev_prev_zero > max_count:
max_index = prev_zero
return max_index
2.8 Merge Interval¶
- In mathematics, a (real) interval is a set of real
- numbers with the property that any number that lies between two numbers in the set is also included in the set.
class Interval:
"""
A set of real numbers with methods to determine if other
numbers are included in the set.
Includes related methods to merge and print interval sets.
"""
def __init__(self, start=0, end=0):
self.start = start
self.end = end
def __repr__(self):
return "Interval ({}, {})".format(self.start, self.end)
def __iter__(self):
return iter(range(self.start, self.end))
def __getitem__(self, index):
if index < 0:
return self.end + index
return self.start + index
def __len__(self):
return self.end - self.start
def __contains__(self, item):
if self.start >= item >= self.end:
return True
return False
def __eq__(self, other):
if self.start == other.start and self.end == other.end:
return True
return False
def as_list(self):
""" Return interval as list. """
return list(self)
@staticmethod
def merge(intervals):
""" Merge two intervals into one. """
out = []
for i in sorted(intervals, key=lambda i: i.start):
if out and i.start <= out[-1].end:
out[-1].end = max(out[-1].end, i.end)
else:
out += i,
return out
@staticmethod
def print_intervals(intervals):
""" Print out the intervals. """
res = []
for i in intervals:
res.append(repr(i))
print("".join(res))
def merge_intervals(intervals):
""" Merge intervals in the form of a list. """
if intervals is None:
return None
intervals.sort(key=lambda i: i[0])
out = [intervals.pop(0)]
for i in intervals:
if out[-1][-1] >= i[0]:
out[-1][-1] = max(out[-1][-1], i[-1])
else:
out.append(i)
return out
2.9 missing ranges¶
Find missing ranges between low and high in the given array. Ex) [3, 5] lo=1 hi=10 => answer: [(1, 2), (4, 4), (6, 10)]
def missing_ranges(arr, lo, hi):
res = []
start = lo
for n in arr:
if n == start:
start += 1
elif n > start:
res.append((start, n-1))
start = n + 1
if start <= hi: # after done iterating thru array,
res.append((start, hi)) # append remainder to list
return res
2.10 move zeros¶
Write an algorithm that takes an array and moves all of the zeros to the end, preserving the order of the other elements.
move_zeros([false, 1, 0, 1, 2, 0, 1, 3, "a"]) returns => [false, 1, 1, 2, 1, 3, "a", 0, 0]
The time complexity of the below algorithm is O(n).
def move_zeros(array):
result = []
zeros = 0
for i in array:
if i is 0: # not using `not i` to avoid `False`, `[]`, etc.
zeros += 1
else:
result.append(i)
result.extend([0] * zeros)
return result
2.11 n sum¶
Given an array of n integers, are there elements a, b, .. , n in nums such that a + b + .. + n = target?
Find all unique n-tuplets in the array which gives the sum of target.
- Example:
- basic:
- Given:
- n = 4 nums = [1, 0, -1, 0, -2, 2] target = 0,
return [[-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1]]
- advanced:
- Given:
n = 2 nums = [[-3, 0], [-2, 1], [2, 2], [3, 3], [8, 4], [-9, 5]] target = -5 def sum(a, b):
return [a[0] + b[1], a[1] + b[0]]- def compare(num, target):
- if num[0] < target:
- return -1
- elif if num[0] > target:
- return 1
- else:
- return 0
return [[-9, 5], [8, 4]]
(TL:DR) because -9 + 4 = -5
def n_sum(n, nums, target, **kv):
"""
n: int
nums: list[object]
target: object
sum_closure: function, optional
Given two elements of nums, return sum of both.
compare_closure: function, optional
Given one object of nums and target, return -1, 1, or 0.
same_closure: function, optional
Given two object of nums, return bool.
return: list[list[object]]
Note:
1. type of sum_closure's return should be same
as type of compare_closure's first param
"""
def sum_closure_default(a, b):
return a + b
def compare_closure_default(num, target):
""" above, below, or right on? """
if num < target:
return -1
elif num > target:
return 1
else:
return 0
def same_closure_default(a, b):
return a == b
def n_sum(n, nums, target):
if n == 2: # want answers with only 2 terms? easy!
results = two_sum(nums, target)
else:
results = []
prev_num = None
for index, num in enumerate(nums):
if prev_num is not None and \
same_closure(prev_num, num):
continue
prev_num = num
n_minus1_results = (
n_sum( # recursive call
n - 1, # a
nums[index + 1:], # b
target - num # c
) # x = n_sum( a, b, c )
) # n_minus1_results = x
n_minus1_results = (
append_elem_to_each_list(num, n_minus1_results)
)
results += n_minus1_results
return union(results)
def two_sum(nums, target):
nums.sort()
lt = 0
rt = len(nums) - 1
results = []
while lt < rt:
sum_ = sum_closure(nums[lt], nums[rt])
flag = compare_closure(sum_, target)
if flag == -1:
lt += 1
elif flag == 1:
rt -= 1
else:
results.append(sorted([nums[lt], nums[rt]]))
lt += 1
rt -= 1
while (lt < len(nums) and
same_closure(nums[lt - 1], nums[lt])):
lt += 1
while (0 <= rt and
same_closure(nums[rt], nums[rt + 1])):
rt -= 1
return results
def append_elem_to_each_list(elem, container):
results = []
for elems in container:
elems.append(elem)
results.append(sorted(elems))
return results
def union(duplicate_results):
results = []
if len(duplicate_results) != 0:
duplicate_results.sort()
results.append(duplicate_results[0])
for result in duplicate_results[1:]:
if results[-1] != result:
results.append(result)
return results
sum_closure = kv.get('sum_closure', sum_closure_default)
same_closure = kv.get('same_closure', same_closure_default)
compare_closure = kv.get('compare_closure', compare_closure_default)
nums.sort()
return n_sum(n, nums, target)
2.12 plus one¶
Given a non-negative number represented as an array of digits, adding one to each numeral.
The digits are stored big-endian, such that the most significant digit is at the head of the list.
def plus_one_v1(digits):
"""
:type digits: List[int]
:rtype: List[int]
"""
digits[-1] = digits[-1] + 1
res = []
ten = 0
i = len(digits)-1
while i >= 0 or ten == 1:
summ = 0
if i >= 0:
summ += digits[i]
if ten:
summ += 1
res.append(summ % 10)
ten = summ // 10
i -= 1
return res[::-1]
def plus_one_v2(digits):
n = len(digits)
for i in range(n-1, -1, -1):
if digits[i] < 9:
digits[i] += 1
return digits
digits[i] = 0
digits.insert(0, 1)
return digits
def plus_one_v3(num_arr):
for idx in reversed(list(enumerate(num_arr))):
num_arr[idx[0]] = (num_arr[idx[0]] + 1) % 10
if num_arr[idx[0]]:
return num_arr
return [1] + num_arr
2.13 rotate¶
Rotate an array of n elements to the right by k steps.
For example, with n = 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4].
Note: Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.
def rotate_v1(array, k):
"""
Rotate the entire array 'k' times
T(n)- O(nk)
:type array: List[int]
:type k: int
:rtype: void Do not return anything, modify array in-place instead.
"""
array = array[:]
n = len(array)
for i in range(k): # unused variable is not a problem
temp = array[n - 1]
for j in range(n-1, 0, -1):
array[j] = array[j - 1]
array[0] = temp
return array
def rotate_v2(array, k):
"""
Reverse segments of the array, followed by the entire array
T(n)- O(n)
:type array: List[int]
:type k: int
:rtype: void Do not return anything, modify nums in-place instead.
"""
array = array[:]
def reverse(arr, a, b):
while a < b:
arr[a], arr[b] = arr[b], arr[a]
a += 1
b -= 1
n = len(array)
k = k % n
reverse(array, 0, n - k - 1)
reverse(array, n - k, n - 1)
reverse(array, 0, n - 1)
return array
def rotate_v3(array, k):
if array is None:
return None
length = len(array)
k = k % length
return array[length - k:] + array[:length - k]
2.14 summarize ranges¶
Given a sorted integer array without duplicates, return the summary of its ranges.
For example, given [0, 1, 2, 4, 5, 7], return [(0, 2), (4, 5), (7, 7)].
def summarize_ranges(array):
"""
:type array: List[int]
:rtype: List[]
"""
res = []
if len(array) == 1:
return [str(array[0])]
i = 0
while i < len(array):
num = array[i]
while i + 1 < len(array) and array[i + 1] - array[i] == 1:
i += 1
if array[i] != num:
res.append((num, array[i]))
else:
res.append((num, num))
i += 1
return res
2.15 three sum¶
Given an array S of n integers, are there three distinct elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note: The solution set must not contain duplicate triplets.
For example, given array S = [-1, 0, 1, 2, -1, -4],
A solution set is: {
(-1, 0, 1), (-1, -1, 2)
}
def three_sum(array):
"""
:param array: List[int]
:return: Set[ Tuple[int, int, int] ]
"""
res = set()
array.sort()
for i in range(len(array) - 2):
if i > 0 and array[i] == array[i - 1]:
continue
l, r = i + 1, len(array) - 1
while l < r:
s = array[i] + array[l] + array[r]
if s > 0:
r -= 1
elif s < 0:
l += 1
else:
# found three sum
res.add((array[i], array[l], array[r]))
# remove duplicates
while l < r and array[l] == array[l + 1]:
l += 1
while l < r and array[r] == array[r - 1]:
r -= 1
l += 1
r -= 1
return res
2.16 top 1¶
This algorithm receives an array and returns most_frequent_value Also, sometimes it is possible to have multiple 'most_frequent_value's, so this function returns a list. This result can be used to find a representative value in an array.
- This algorithm gets an array, makes a dictionary of it,
- finds the most frequent count, and makes the result list.
For example: top_1([1, 1, 2, 2, 3, 4]) will return [1, 2]
(TL:DR) Get mathematical Mode Complexity: O(n)
def top_1(arr):
values = {}
#reserve each value which first appears on keys
#reserve how many time each value appears by index number on values
result = []
f_val = 0
for i in arr:
if i in values:
values[i] += 1
else:
values[i] = 1
f_val = max(values.values())
for i in values.keys():
if values[i] == f_val:
result.append(i)
else:
continue
return result
2.17 trim mean¶
When make reliable means, we need to neglect best and worst values. For example, when making average score on athletes we need this option. So, this algorithm affixes some percentage to neglect when making mean. For example, if you suggest 20%, it will neglect the best 10% of values and the worst 10% of values.
This algorithm takes an array and percentage to neglect. After sorted, if index of array is larger or smaller than desired ratio, we don't compute it.
Compleity: O(n)
def trimmean(arr, per):
ratio = per/200
# /100 for easy calculation by *, and /2 for easy adaption to best and worst parts.
cal_sum = 0
# sum value to be calculated to trimmean.
arr.sort()
neg_val = int(len(arr)*ratio)
arr = arr[neg_val:len(arr)-neg_val]
for i in arr:
cal_sum += i
return cal_sum/len(arr)
2.18 two sum¶
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
- Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9, return (0, 1)
def two_sum(array, target):
dic = {}
for i, num in enumerate(array):
if num in dic:
return dic[num], i
else:
dic[target - num] = i
return None
chapter 3: Backtrack¶
3.1 add operators¶
Given a string that contains only digits 0-9 and a target value, return all possibilities to add binary operators (not unary) +, -, or * between the digits so they prevuate to the target value.
Examples: "123", 6 -> ["1+2+3", "1*2*3"] "232", 8 -> ["2*3+2", "2+3*2"] "105", 5 -> ["1*0+5","10-5"] "00", 0 -> ["0+0", "0-0", "0*0"] "3456237490", 9191 -> []
def add_operators(num, target):
"""
:type num: str
:type target: int
:rtype: List[str]
"""
def dfs(res, path, num, target, pos, prev, multed):
if pos == len(num):
if target == prev:
res.append(path)
return
for i in range(pos, len(num)):
if i != pos and num[pos] == '0': # all digits have to be used
break
cur = int(num[pos:i+1])
if pos == 0:
dfs(res, path + str(cur), num, target, i+1, cur, cur)
else:
dfs(res, path + "+" + str(cur), num, target,
i+1, prev + cur, cur)
dfs(res, path + "-" + str(cur), num, target,
i+1, prev - cur, -cur)
dfs(res, path + "*" + str(cur), num, target,
i+1, prev - multed + multed * cur, multed * cur)
res = []
if not num:
return res
dfs(res, "", num, target, 0, 0, 0)
return res
3.2 anagram¶
def anagram(s1, s2):
c1 = [0] * 26
c2 = [0] * 26
for c in s1:
pos = ord(c)-ord('a')
c1[pos] = c1[pos] + 1
for c in s2:
pos = ord(c)-ord('a')
c2[pos] = c2[pos] + 1
return c1 == c2
3.3 array sum combination¶
WAP to take one element from each of the array add it to the target sum. Print all those three-element combinations.
/* A = [1, 2, 3, 3] B = [2, 3, 3, 4] C = [2, 3, 3, 4] target = 7 */
Result: [[1, 2, 4], [1, 3, 3], [1, 3, 3], [1, 3, 3], [1, 3, 3], [1, 4, 2],
[2, 2, 3], [2, 2, 3], [2, 3, 2], [2, 3, 2], [3, 2, 2], [3, 2, 2]]
import itertools
from functools import partial
def array_sum_combinations(A, B, C, target):
def over(constructed_sofar):
sum = 0
to_stop, reached_target = False, False
for elem in constructed_sofar:
sum += elem
if sum >= target or len(constructed_sofar) >= 3:
to_stop = True
if sum == target and 3 == len(constructed_sofar):
reached_target = True
return to_stop, reached_target
def construct_candidates(constructed_sofar):
array = A
if 1 == len(constructed_sofar):
array = B
elif 2 == len(constructed_sofar):
array = C
return array
def backtrack(constructed_sofar=[], res=[]):
to_stop, reached_target = over(constructed_sofar)
if to_stop:
if reached_target:
res.append(constructed_sofar)
return
candidates = construct_candidates(constructed_sofar)
for candidate in candidates:
constructed_sofar.append(candidate)
backtrack(constructed_sofar[:], res)
constructed_sofar.pop()
res = []
backtrack([], res)
return res
def unique_array_sum_combinations(A, B, C, target):
"""
1. Sort all the arrays - a,b,c. - This improves average time complexity.
2. If c[i] < Sum, then look for Sum - c[i] in array a and b.
When pair found, insert c[i], a[j] & b[k] into the result list.
This can be done in O(n).
3. Keep on doing the above procedure while going through complete c array.
Complexity: O(n(m+p))
"""
def check_sum(n, *nums):
if sum(x for x in nums) == n:
return (True, nums)
else:
return (False, nums)
pro = itertools.product(A, B, C)
func = partial(check_sum, target)
sums = list(itertools.starmap(func, pro))
res = set()
for s in sums:
if s[0] is True and s[1] not in res:
res.add(s[1])
return list(res)
3.4 combination sum¶
Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Note: All numbers (including target) will be positive integers. The solution set must not contain duplicate combinations. For example, given candidate set [2, 3, 6, 7] and target 7, A solution set is: [
[7], [2, 2, 3]
]
def combination_sum(candidates, target):
def dfs(nums, target, index, path, res):
if target < 0:
return # backtracking
if target == 0:
res.append(path)
return
for i in range(index, len(nums)):
dfs(nums, target-nums[i], i, path+[nums[i]], res)
res = []
candidates.sort()
dfs(candidates, target, 0, [], res)
return res
3.5 factor combinations¶
Numbers can be regarded as product of its factors. For example,
- 8 = 2 x 2 x 2;
- = 2 x 4.
Write a function that takes an integer n and return all possible combinations of its factors.
Note: You may assume that n is always positive. Factors should be greater than 1 and less than n. Examples: input: 1 output: [] input: 37 output: [] input: 12 output: [
[2, 6], [2, 2, 3], [3, 4]
] input: 32 output: [
[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], [2, 4, 4], [4, 8]
]
# Iterative:
def get_factors(n):
todo, combis = [(n, 2, [])], []
while todo:
n, i, combi = todo.pop()
while i * i <= n:
if n % i == 0:
combis.append(combi + [i, n//i])
todo.append((n//i, i, combi+[i]))
i += 1
return combis
# Recursive:
def recursive_get_factors(n):
def factor(n, i, combi, combis):
while i * i <= n:
if n % i == 0:
combis.append(combi + [i, n//i]),
factor(n//i, i, combi+[i], combis)
i += 1
return combis
return factor(n, 2, [], [])
3.6 find words¶
Given a matrix of words and a list of words to search, return a list of words that exists in the board This is Word Search II on LeetCode
- board = [
- ['o','a','a','n'], ['e','t','a','e'], ['i','h','k','r'], ['i','f','l','v'] ]
words = ["oath","pea","eat","rain"]
def find_words(board, words):
def backtrack(board, i, j, trie, pre, used, result):
'''
backtrack tries to build each words from
the board and return all words found
@param: board, the passed in board of characters
@param: i, the row index
@param: j, the column index
@param: trie, a trie of the passed in words
@param: pre, a buffer of currently build string that differs
by recursion stack
@param: used, a replica of the board except in booleans
to state whether a character has been used
@param: result, the resulting set that contains all words found
@return: list of words found
'''
if '#' in trie:
result.add(pre)
if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]):
return
if not used[i][j] and board[i][j] in trie:
used[i][j] = True
backtrack(board, i+1, j, trie[board[i][j]],
pre+board[i][j], used, result)
backtrack(board, i, j+1, trie[board[i][j]],
pre+board[i][j], used, result)
backtrack(board, i-1, j, trie[board[i][j]],
pre+board[i][j], used, result)
backtrack(board, i, j-1, trie[board[i][j]],
pre+board[i][j], used, result)
used[i][j] = False
# make a trie structure that is essentially dictionaries of dictionaries
# that map each character to a potential next character
trie = {}
for word in words:
curr_trie = trie
for char in word:
if char not in curr_trie:
curr_trie[char] = {}
curr_trie = curr_trie[char]
curr_trie['#'] = '#'
# result is a set of found words since we do not want repeats
result = set()
used = [[False]*len(board[0]) for _ in range(len(board))]
for i in range(len(board)):
for j in range(len(board[0])):
backtrack(board, i, j, trie, '', used, result)
return list(result)
3.7 generate abbreviations¶
given input word, return the list of abbreviations. ex) word => [1ord, w1rd, wo1d, w2d, 3d, w3 ... etc]
def generate_abbreviations(word):
def backtrack(result, word, pos, count, cur):
if pos == len(word):
if count > 0:
cur += str(count)
result.append(cur)
return
if count > 0: # add the current word
backtrack(result, word, pos+1, 0, cur+str(count)+word[pos])
else:
backtrack(result, word, pos+1, 0, cur+word[pos])
# skip the current word
backtrack(result, word, pos+1, count+1, cur)
result = []
backtrack(result, word, 0, 0, "")
return result
3.8 generate parenthesis¶
Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
For example, given n = 3, a solution set is:
- [
- "((()))", "(()())", "(())()", "()(())", "()()()"
]
def generate_parenthesis_v1(n):
def add_pair(res, s, left, right):
if left == 0 and right == 0:
res.append(s)
return
if right > 0:
add_pair(res, s + ")", left, right - 1)
if left > 0:
add_pair(res, s + "(", left - 1, right + 1)
res = []
add_pair(res, "", n, 0)
return res
def generate_parenthesis_v2(n):
def add_pair(res, s, left, right):
if left == 0 and right == 0:
res.append(s)
if left > 0:
add_pair(res, s + "(", left - 1, right)
if right > 0 and left < right:
add_pair(res, s + ")", left, right - 1)
res = []
add_pair(res, "", n, n)
return res
3.9 letter combination¶
Given a digit string, return all possible letter combinations that the number could represent.
Input:Digit string "23" Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
def letter_combinations(digits):
if digits == "":
return []
kmaps = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz"
}
ans = [""]
for num in digits:
tmp = []
for an in ans:
for char in kmaps[num]:
tmp.append(an + char)
ans = tmp
return ans
3.10 palindrome partioning¶
It looks like you need to be looking not for all palindromic substrings, but rather for all the ways you can divide the input string up into palindromic substrings. (There's always at least one way, since one-character substrings are always palindromes.)
def palindromic_substrings(s):
if not s:
return [[]]
results = []
for i in range(len(s), 0, -1):
sub = s[:i]
if sub == sub[::-1]:
for rest in palindromic_substrings(s[i:]):
results.append([sub] + rest)
return results
There's two loops. The outer loop checks each length of initial substring (in descending length order) to see if it is a palindrome. If so, it recurses on the rest of the string and loops over the returned values, adding the initial substring to each item before adding it to the results.
def palindromic_substrings_iter(s):
"""
A slightly more Pythonic approach with a recursive generator
"""
if not s:
yield []
return
for i in range(len(s), 0, -1):
sub = s[:i]
if sub == sub[::-1]:
for rest in palindromic_substrings_iter(s[i:]):
yield [sub] + rest
3.11 pattern match¶
Given a pattern and a string str, find if str follows the same pattern.
Here follow means a full match, such that there is a bijection between a letter in pattern and a non-empty substring in str.
Examples: pattern = "abab", str = "redblueredblue" should return true. pattern = "aaaa", str = "asdasdasdasd" should return true. pattern = "aabb", str = "xyzabcxzyabc" should return false. Notes: You may assume both pattern and str contains only lowercase letters.
def pattern_match(pattern, string):
"""
:type pattern: str
:type string: str
:rtype: bool
"""
def backtrack(pattern, string, dic):
if len(pattern) == 0 and len(string) > 0:
return False
if len(pattern) == len(string) == 0:
return True
for end in range(1, len(string)-len(pattern)+2):
if pattern[0] not in dic and string[:end] not in dic.values():
dic[pattern[0]] = string[:end]
if backtrack(pattern[1:], string[end:], dic):
return True
del dic[pattern[0]]
elif pattern[0] in dic and dic[pattern[0]] == string[:end]:
if backtrack(pattern[1:], string[end:], dic):
return True
return False
return backtrack(pattern, string, {})
3.12 Permute Unique¶
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
For example, [1,1,2] have the following unique permutations: [
[1,1,2], [1,2,1], [2,1,1]
]
def permute_unique(nums):
perms = [[]]
for n in nums:
new_perms = []
for l in perms:
for i in range(len(l)+1):
new_perms.append(l[:i]+[n]+l[i:])
if i < len(l) and l[i] == n:
break # handles duplication
perms = new_perms
return perms
3.13 Permute¶
Given a collection of distinct numbers, return all possible permutations.
For example, [1,2,3] have the following permutations: [
[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]
]
def permute(elements):
"""
returns a list with the permuations.
"""
if len(elements) <= 1:
return elements
else:
tmp = []
for perm in permute(elements[1:]):
for i in range(len(elements)):
tmp.append(perm[:i] + elements[0:1] + perm[i:])
return tmp
def permute_iter(elements):
"""
iterator: returns a perumation by each call.
"""
if len(elements) <= 1:
yield elements
else:
for perm in permute_iter(elements[1:]):
for i in range(len(elements)):
yield perm[:i] + elements[0:1] + perm[i:]
# DFS Version
def permute_recursive(nums):
def dfs(res, nums, path):
if not nums:
res.append(path)
for i in range(len(nums)):
print(nums[:i]+nums[i+1:])
dfs(res, nums[:i]+nums[i+1:], path+[nums[i]])
res = []
dfs(res, nums, [])
return res
3.14 subsets unique¶
Given a collection of integers that might contain duplicates, nums, return all possible subsets.
Note: The solution set must not contain duplicate subsets.
For example, If nums = [1,2,2], a solution is:
- [
- [2], [1], [1,2,2], [2,2], [1,2], []
]
def subsets_unique(nums):
def backtrack(res, nums, stack, pos):
if pos == len(nums):
res.add(tuple(stack))
else:
# take
stack.append(nums[pos])
backtrack(res, nums, stack, pos+1)
stack.pop()
# don't take
backtrack(res, nums, stack, pos+1)
res = set()
backtrack(res, nums, [], 0)
return list(res)
3.15 subsets¶
Given a set of distinct integers, nums, return all possible subsets.
Note: The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,3], a solution is:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
chapter 4: bfs¶
4.1 maze search¶
BFS time complexity : O(|E|) BFS space complexity : O(|V|)
do BFS from (0,0) of the grid and get the minimum number of steps needed to get to the lower right column
only step on the columns whose value is 1
if there is no path, it returns -1
def maze_search(grid):
dx = [0,0,-1,1]
dy = [-1,1,0,0]
n = len(grid)
m = len(grid[0])
q = [(0,0,0)]
visit = [[0]*m for _ in range(n)]
if grid[0][0] == 0:
return -1
visit[0][0] = 1
while q:
i, j, step = q.pop(0)
if i == n-1 and j == m-1:
return step
for k in range(4):
x = i + dx[k]
y = j + dy[k]
if x>=0 and x<n and y>=0 and y<m:
if grid[x][y] ==1 and visit[x][y] == 0:
visit[x][y] = 1
q.append((x,y,step+1))
return -1
4.2 shortest distance from alll buidings¶
do BFS from each building, and decrement all empty place for every building visit when grid[i][j] == -b_nums, it means that grid[i][j] are already visited from all b_nums and use dist to record distances from b_nums
import collections
def shortest_distance(grid):
if not grid or not grid[0]:
return -1
matrix = [[[0,0] for i in range(len(grid[0]))] for j in range(len(grid))]
count = 0 # count how many building we have visited
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
bfs(grid, matrix, i, j, count)
count += 1
res = float('inf')
for i in range(len(matrix)):
for j in range(len(matrix[0])):
if matrix[i][j][1]==count:
res = min(res, matrix[i][j][0])
return res if res!=float('inf') else -1
def bfs(grid, matrix, i, j, count):
q = [(i, j, 0)]
while q:
i, j, step = q.pop(0)
for k, l in [(i-1,j), (i+1,j), (i,j-1), (i,j+1)]:
# only the position be visited by count times will append to queue
if 0<=k<len(grid) and 0<=l<len(grid[0]) and \
matrix[k][l][1]==count and grid[k][l]==0:
matrix[k][l][0] += step+1
matrix[k][l][1] = count+1
q.append((k, l, step+1))
4.3 word ladder¶
Given two words (begin_word and end_word), and a dictionary's word list, find the length of shortest transformation sequence from beginWord to endWord, such that:
Only one letter can be changed at a time Each intermediate word must exist in the word list For example,
Given: begin_word = "hit" end_word = "cog" word_list = ["hot","dot","dog","lot","log"] As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", return its length 5. . Note: Return -1 if there is no such transformation sequence. All words have the same length. All words contain only lowercase alphabetic characters.
import unittest
def ladder_length(begin_word, end_word, word_list):
"""
Bidirectional BFS!!!
:type begin_word: str
:type end_word: str
:type word_list: Set[str]
:rtype: int
"""
if len(begin_word) != len(end_word):
return -1 # not possible
if begin_word == end_word:
return 0
# when only differ by 1 character
if sum(c1 != c2 for c1, c2 in zip(begin_word, end_word)) == 1:
return 1
begin_set = set()
end_set = set()
begin_set.add(begin_word)
end_set.add(end_word)
result = 2
while begin_set and end_set:
if len(begin_set) > len(end_set):
begin_set, end_set = end_set, begin_set
next_begin_set = set()
for word in begin_set:
for ladder_word in word_range(word):
if ladder_word in end_set:
return result
if ladder_word in word_list:
next_begin_set.add(ladder_word)
word_list.remove(ladder_word)
begin_set = next_begin_set
result += 1
# print(begin_set)
# print(result)
return -1
def word_range(word):
for ind in range(len(word)):
temp = word[ind]
for c in [chr(x) for x in range(ord('a'), ord('z') + 1)]:
if c != temp:
yield word[:ind] + c + word[ind + 1:]
class TestSuite(unittest.TestCase):
def test_ladder_length(self):
# hit -> hot -> dot -> dog -> cog
self.assertEqual(5, ladder_length('hit', 'cog', ["hot", "dot", "dog", "lot", "log"]))
# pick -> sick -> sink -> sank -> tank == 5
self.assertEqual(5, ladder_length('pick', 'tank',
['tock', 'tick', 'sank', 'sink', 'sick']))
# live -> life == 1, no matter what is the word_list.
self.assertEqual(1, ladder_length('live', 'life', ['hoho', 'luck']))
# 0 length from ate -> ate
self.assertEqual(0, ladder_length('ate', 'ate', []))
# not possible to reach !
self.assertEqual(-1, ladder_length('rahul', 'coder', ['blahh', 'blhah']))
if __name__ == '__main__':
unittest.main()
chapter 5: Bit manipulation¶
5.1 add bitwise operator¶
The following code adds two positive integers without using the '+' operator. The code uses bitwise operations to add two numbers.
Input: 2 3 Output: 5
def add_bitwise_operator(x, y):
while y:
carry = x & y
x = x ^ y
y = carry << 1
return x
5.2 binary gap¶
Given a positive integer N, find and return the longest distance between two consecutive 1' in the binary representation of N. If there are not two consecutive 1's, return 0
For example: Input: 22 Output: 2 Explanation: 22 in binary is 10110 In the binary representation of 22, there are three ones, and two consecutive pairs of 1's. The first consecutive pair of 1's have distance 2. The second consecutive pair of 1's have distance 1. The answer is the largest of these two distances, which is 2
def binary_gap(N):
last = None
ans = 0
index = 0
while N != 0:
if N & 1:
if last is not None:
ans = max(ans, index - last)
last = index
index = index + 1
N = N >> 1
return ans
5.3 bit operation¶
- Fundamental bit operation:
- get_bit(num, i): get an exact bit at specific index set_bit(num, i): set a bit at specific index clear_bit(num, i): clear a bit at specific index update_bit(num, i, bit): update a bit at specific index
This function shifts 1 over by i bits, creating a value being like 0001000. By performing an AND with num, we clear all bits other than the bit at bit i. Finally we compare that to 0
def get_bit(num, i):
return (num & (1 << i)) != 0
This method operates in almost the reverse of set_bit
def clear_bit(num, i):
mask = ~(1 << i)
return num & mask
To set the ith bit to value, we first clear the bit at position i by using a mask. Then, we shift the intended value. Finally we OR these two numbers
def update_bit(num, i, bit):
mask = ~(1 << i)
return (num & mask) | (bit << i)
5.4 bytes int conversion¶
from collections import deque
def int_to_bytes_big_endian(num):
bytestr = deque()
while num > 0:
# list.insert(0, ...) is inefficient
bytestr.appendleft(num & 0xff)
num >>= 8
return bytes(bytestr)
def int_to_bytes_little_endian(num):
bytestr = []
while num > 0:
bytestr.append(num & 0xff)
num >>= 8
return bytes(bytestr)
def bytes_big_endian_to_int(bytestr):
num = 0
for b in bytestr:
num <<= 8
num += b
return num
def bytes_little_endian_to_int(bytestr):
num = 0
e = 0
for b in bytestr:
num += b << e
e += 8
return num
5.5 count filps to convert¶
Write a function to determine the number of bits you would need to flip to convert integer A to integer B. For example: Input: 29 (or: 11101), 15 (or: 01111) Output: 2
def count_flips_to_convert(a, b):
diff = a ^ b
# count number of ones in diff
count = 0
while diff:
diff &= (diff - 1)
count += 1
return count
5.6 count ones¶
Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also known as the Hamming weight).
For example, the 32-bit integer ’11' has binary representation 00000000000000000000000000001011, so the function should return 3.
T(n)- O(k) : k is the number of 1s present in binary representation. NOTE: this complexity is better than O(log n). e.g. for n = 00010100000000000000000000000000 only 2 iterations are required.
Number of loops is equal to the number of 1s in the binary representation
def count_ones_recur(n):
"""Using Brian Kernighan’s Algorithm. (Recursive Approach)"""
if not n:
return 0
return 1 + count_ones_recur(n & (n-1))
def count_ones_iter(n):
"""Using Brian Kernighan’s Algorithm. (Iterative Approach)"""
count = 0
while n:
n &= (n-1)
count += 1
return count
5.7 find difference¶
Given two strings s and t which consist of only lowercase letters. String t is generated by random shuffling string s and then add one more letter at a random position. Find the letter that was added in t.
For example: Input: s = "abcd" t = "abecd" Output: 'e'
Explanation: 'e' is the letter that was added. """
""" We use the characteristic equation of XOR. A xor B xor C = A xor C xor B If A == C, then A xor C = 0 and then, B xor 0 = B
def find_difference(s, t):
ret = 0
for ch in s + t:
# ord(ch) return an integer representing the Unicode code point of that character
ret = ret ^ ord(ch)
# chr(i) Return the string representing a character whose Unicode code point is the integer i
return chr(ret)
5.8 find missing number¶
Returns the missing number from a sequence of unique integers in range [0..n] in O(n) time and space. The difference between consecutive integers cannot be more than 1. If the sequence is already complete, the next integer in the sequence will be returned.
def find_missing_number(nums):
missing = 0
for i, num in enumerate(nums):
missing ^= num
missing ^= i + 1
return missing
def find_missing_number2(nums):
num_sum = sum(nums)
n = len(nums)
total_sum = n*(n+1) // 2
missing = total_sum - num_sum
return missing
5.9 flip bit longest sequence¶
You have an integer and you can flip exactly one bit from a 0 to 1. Write code to find the length of the longest sequence of 1s you could create. For example: Input: 1775 ( or: 11011101111) Output: 8
def flip_bit_longest_seq(num):
curr_len = 0
prev_len = 0
max_len = 0
while num:
if num & 1 == 1: # last digit is 1
curr_len += 1
elif num & 1 == 0: # last digit is 0
if num & 2 == 0: # second last digit is 0
prev_len = 0
else:
prev_len = curr_len
curr_len = 0
max_len = max(max_len, prev_len + curr_len)
num = num >> 1 # right shift num
return max_len + 1
5.10 has alternative bit¶
Given a positive integer, check whether it has alternating bits: namely, if two adjacent bits will always have different values.
For example: Input: 5 Output: True because the binary representation of 5 is: 101.
Input: 7 Output: False because the binary representation of 7 is: 111.
Input: 11 Output: False because the binary representation of 11 is: 1011.
Input: 10 Output: True because The binary representation of 10 is: 1010.
# Time Complexity - O(number of bits in n)
def has_alternative_bit(n):
first_bit = 0
second_bit = 0
while n:
first_bit = n & 1
if n >> 1:
second_bit = (n >> 1) & 1
if not first_bit ^ second_bit:
return False
else:
return True
n = n >> 1
return True
# Time Complexity - O(1)
def has_alternative_bit_fast(n):
mask1 = int('aaaaaaaa', 16) # for bits ending with zero (...1010)
mask2 = int('55555555', 16) # for bits ending with one (...0101)
return mask1 == (n + (n ^ mask1)) or mask2 == (n + (n ^ mask2))
5.11 insert bit¶
Insertion:
insert_one_bit(num, bit, i): insert exact one bit at specific position For example:
Input: num = 10101 (21) insert_one_bit(num, 1, 2): 101101 (45) insert_one_bit(num, 0 ,2): 101001 (41) insert_one_bit(num, 1, 5): 110101 (53) insert_one_bit(num, 1, 0): 101011 (43)
insert_mult_bits(num, bits, len, i): insert multiple bits with len at specific position For example:
Input: num = 101 (5) insert_mult_bits(num, 7, 3, 1): 101111 (47) insert_mult_bits(num, 7, 3, 0): 101111 (47) insert_mult_bits(num, 7, 3, 3): 111101 (61)
Insert exact one bit at specific position
Algorithm: 1. Create a mask having bit from i to the most significant bit, and append the new bit at 0 position 2. Keep the bit from 0 position to i position ( like 000...001111) 3) Merge mask and num
def insert_one_bit(num, bit, i):
# Create mask
mask = num >> i
mask = (mask << 1) | bit
mask = mask << i
# Keep the bit from 0 position to i position
right = ((1 << i) - 1) & num
return right | mask
def insert_mult_bits(num, bits, len, i):
mask = num >> i
mask = (mask << len) | bits
mask = mask << i
right = ((1 << i) - 1) & num
return right | mask
5.12 Power of two¶
given an integer, write a function to determine if it is a power of two
def is_power_of_two(n):
"""
:type n: int
:rtype: bool
"""
return n > 0 and not n & (n-1)
5.13 Remove bit¶
Remove_bit(num, i): remove a bit at specific position. For example:
Input: num = 10101 (21) remove_bit(num, 2): output = 1001 (9) remove_bit(num, 4): output = 101 (5) remove_bit(num, 0): output = 1010 (10)
def remove_bit(num, i):
mask = num >> (i + 1)
mask = mask << i
right = ((1 << i) - 1) & num
return mask | right
5.14 Reverse bits¶
Reverse bits of a given 32 bits unsigned integer.
For example, given input 43261596 (represented in binary as 00000010100101000001111010011100), return 964176192 (represented in binary as 00111001011110000010100101000000).
def reverse_bits(n):
m = 0
i = 0
while i < 32:
m = (m << 1) + (n & 1)
n >>= 1
i += 1
return m
5.15 Single number¶
Given an array of integers, every element appears twice except for one. Find that single one.
- NOTE: This also works for finding a number occurring odd
- number of times, where all the other numbers appear even number of times.
Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
def single_number(nums):
"""
Returns single number, if found.
Else if all numbers appear twice, returns 0.
:type nums: List[int]
:rtype: int
"""
i = 0
for num in nums:
i ^= num
return i
5.16 Single number2¶
Given an array of integers, every element appears three times except for one, which appears exactly once. Find that single one.
Note: Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
Solution: 32 bits for each integer. Consider 1 bit in it, the sum of each integer's corresponding bit (except for the single number) should be 0 if mod by 3. Hence, we sum the bits of all integers and mod by 3, the remaining should be the exact bit of the single number. In this way, you get the 32 bits of the single number.
# Another awesome answer
def single_number2(nums):
ones, twos = 0, 0
for i in range(len(nums)):
ones = (ones ^ nums[i]) & ~twos
twos = (twos ^ nums[i]) & ~ones
return ones
5.17 Single number3¶
Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once. Limitation: Time Complexity: O(N) and Space Complexity O(1)
For example:
Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].
Note: The order of the result is not important. So in the above example, [5, 3] is also correct.
Solution: 1. Use XOR to cancel out the pairs and isolate A^B 2. It is guaranteed that at least 1 bit exists in A^B since
A and B are different numbers. ex) 010 ^ 111 = 101
- Single out one bit R (right most bit in this solution) to use it as a pivot
- Divide all numbers into two groups. One group with a bit in the position R One group without a bit in the position R
- Use the same strategy we used in step 1 to isolate A and B from each group.
def single_number3(nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
# isolate a^b from pairs using XOR
ab = 0
for n in nums:
ab ^= n
# isolate right most bit from a^b
right_most = ab & (-ab)
# isolate a and b from a^b
a, b = 0, 0
for n in nums:
if n & right_most:
a ^= n
else:
b ^= n
return [a, b]
5.18 subsets¶
Given a set of distinct integers, nums, return all possible subsets.
Note: The solution set must not contain duplicate subsets.
For example, If nums = [1,2,3], a solution is:
- {
- (1, 2), (1, 3), (1,), (2,), (3,), (1, 2, 3), (), (2, 3)
}
def subsets(nums):
"""
:param nums: List[int]
:return: Set[tuple]
"""
n = len(nums)
total = 1 << n
res = set()
for i in range(total):
subset = tuple(num for j, num in enumerate(nums) if i & 1 << j)
res.add(subset)
return res
this explanation is from leet_nik @ leetcode This is an amazing solution. Learnt a lot.
Number of subsets for {1 , 2 , 3 } = 2^3 . why ? case possible outcomes for the set of subsets
1 -> Take or dont take = 2 2 -> Take or dont take = 2 3 -> Take or dont take = 2
therefore, total = 2*2*2 = 2^3 = {{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}
Lets assign bits to each outcome -> First bit to 1 , Second bit to 2 and third bit to 3 Take = 1 Dont take = 0
- 0 0 0 -> Dont take 3 , Dont take 2 , Dont take 1 = { }
- 0 0 1 -> Dont take 3 , Dont take 2 , take 1 = { 1 }
- 0 1 0 -> Dont take 3 , take 2 , Dont take 1 = { 2 }
- 0 1 1 -> Dont take 3 , take 2 , take 1 = { 1 , 2 }
- 1 0 0 -> take 3 , Dont take 2 , Dont take 1 = { 3 }
- 1 0 1 -> take 3 , Dont take 2 , take 1 = { 1 , 3 }
- 1 1 0 -> take 3 , take 2 , Dont take 1 = { 2 , 3 }
- 1 1 1 -> take 3 , take 2 , take 1 = { 1 , 2 , 3 }
In the above logic ,Insert S[i] only if (j>>i)&1 ==true { j E { 0,1,2,3,4,5,6,7 } i = ith element in the input array }
element 1 is inserted only into those places where 1st bit of j is 1 if( j >> 0 &1 ) ==> for above above eg. this is true for sl.no.( j )= 1 , 3 , 5 , 7
element 2 is inserted only into those places where 2nd bit of j is 1 if( j >> 1 &1 ) == for above above eg. this is true for sl.no.( j ) = 2 , 3 , 6 , 7
element 3 is inserted only into those places where 3rd bit of j is 1 if( j >> 2 & 1 ) == for above above eg. this is true for sl.no.( j ) = 4 , 5 , 6 , 7
Time complexity : O(n*2^n) , for every input element loop traverses the whole solution set length i.e. 2^n
5.19 swap pair¶
Swap_pair: A function swap odd and even bits in an integer with as few instructions as possible (Ex bit and bit 1 are swapped, bit 2 and bit 3 are swapped)
For example: 22: 010110 --> 41: 101001 10: 1010 --> 5 : 0101 """
""" We can approach this as operating on the odds bit first, and then the even bits. We can mask all odd bits with 10101010 in binary ('AA') then shift them right by 1 Similarly, we mask all even bit with 01010101 in binary ('55') then shift them left by 1. Finally, we merge these two values by OR operation.
def swap_pair(num):
# odd bit arithmetic right shift 1 bit
odd = (num & int('AAAAAAAA', 16)) >> 1
# even bit left shift 1 bit
even = (num & int('55555555', 16)) << 1
return odd | even
chapter 6: Calculator¶
6.1 math parser¶
Contributed by izanbf1803.
Example:¶
- Code:
exp = "2452 * (3 * 6.5 + 1) * 6 / 235"print("Expression:", exp)print("Parsed expression:", mp.parse(exp))print("Evaluation result:", mp.evaluate(exp))- Output:
Expression: 2452 * (3 * 6 + 1) * 6 / 235Parsed expression: ['2452', '*', '(', '3', '*', '6', '+', '1', ')', '*', '6', '/', '235']Evaluation result: 1189.4808510638297
Now added '^' operator for exponents. (by @goswami-rahul)
from collections import deque
import re
numeric_value = re.compile('\d+(\.\d+)?')
__operators__ = "+-/*^"
__parenthesis__ = "()"
__priority__ = {
'+': 0,
'-': 0,
'*': 1,
'/': 1,
'^': 2
}
def is_operator(token):
"""
Check if token it's a operator
token Char: Token
"""
return token in __operators__
def higher_priority(op1, op2):
"""
Check if op1 have higher priority than op2
op1 Char: Operation Token 1
op2 Char: Operation Token 2
"""
return __priority__[op1] >= __priority__[op2]
def calc(n2, n1, operator):
"""
Calculate operation result
n2 Number: Number 2
n1 Number: Number 1
operator Char: Operation to calculate
"""
if operator == '-': return n1 - n2
elif operator == '+': return n1 + n2
elif operator == '*': return n1 * n2
elif operator == '/': return n1 / n2
elif operator == '^': return n1 ** n2
return 0
def apply_operation(op_stack, out_stack):
"""
Apply operation to the first 2 items of the output queue
op_stack Deque (reference)
out_stack Deque (reference)
"""
out_stack.append(calc(out_stack.pop(), out_stack.pop(), op_stack.pop()))
def parse(expression):
"""
Return array of parsed tokens in the expression
expression String: Math expression to parse in infix notation
"""
result = []
current = ""
for i in expression:
if i.isdigit() or i == '.':
current += i
else:
if len(current) > 0:
result.append(current)
current = ""
if i in __operators__ or i in __parenthesis__:
result.append(i)
else:
raise Exception("invalid syntax " + i)
if len(current) > 0:
result.append(current)
return result
def evaluate(expression):
"""
Calculate result of expression
expression String: The expression
type Type (optional): Number type [int, float]
"""
op_stack = deque() # operator stack
out_stack = deque() # output stack (values)
tokens = parse(expression) # calls the function only once!
for token in tokens:
if numeric_value.match(token):
out_stack.append(float(token))
elif token == '(':
op_stack.append(token)
elif token == ')':
while len(op_stack) > 0 and op_stack[-1] != '(':
apply_operation(op_stack, out_stack)
op_stack.pop() # Remove remaining '('
else: # is_operator(token)
while len(op_stack) > 0 and is_operator(op_stack[-1]) and higher_priority(op_stack[-1], token):
apply_operation(op_stack, out_stack)
op_stack.append(token)
while len(op_stack) > 0:
apply_operation(op_stack, out_stack)
return out_stack[-1]
def main():
"""
simple user-interface
"""
print("\t\tCalculator\n\n")
while True:
user_input = input("expression or exit: ")
if user_input == "exit":
break
try:
print("The result is {0}".format(evaluate(user_input)))
except Exception:
print("invalid syntax!")
user_input = input("expression or exit: ")
print("program end")
if __name__ == "__main__":
main()
chapter 7: dfs¶
7.1 all factors¶
Numbers can be regarded as product of its factors. For example, 8 = 2 x 2 x 2;
= 2 x 4.
Write a function that takes an integer n and return all possible combinations of its factors.Numbers can be regarded as product of its factors. For example, 8 = 2 x 2 x 2;
= 2 x 4.
Examples: input: 1 output: []
input: 37 output: []
input: 32 output: [
[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2],
import unittest
def get_factors(n):
"""[summary]
Arguments:
n {[int]} -- [to analysed number]
Returns:
[list of lists] -- [all factors of the number n]
"""
def factor(n, i, combi, res):
"""[summary]
helper function
Arguments:
n {[int]} -- [number]
i {[int]} -- [to tested divisor]
combi {[list]} -- [catch divisors]
res {[list]} -- [all factors of the number n]
Returns:
[list] -- [res]
"""
while i * i <= n:
if n % i == 0:
res += combi + [i, int(n/i)],
factor(n/i, i, combi+[i], res)
i += 1
return res
return factor(n, 2, [], [])
def get_factors_iterative1(n):
"""[summary]
Computes all factors of n.
Translated the function get_factors(...) in
a call-stack modell.
Arguments:
n {[int]} -- [to analysed number]
Returns:
[list of lists] -- [all factors]
"""
todo, res = [(n, 2, [])], []
while todo:
n, i, combi = todo.pop()
while i * i <= n:
if n % i == 0:
res += combi + [i, n//i],
todo.append((n//i, i, combi+[i])),
i += 1
return res
def get_factors_iterative2(n):
"""[summary]
analog as above
Arguments:
n {[int]} -- [description]
Returns:
[list of lists] -- [all factors of n]
"""
ans, stack, x = [], [], 2
while True:
if x > n // x:
if not stack:
return ans
ans.append(stack + [n])
x = stack.pop()
n *= x
x += 1
elif n % x == 0:
stack.append(x)
n //= x
else:
x += 1
class TestAllFactors(unittest.TestCase):
def test_get_factors(self):
self.assertEqual([[2, 16], [2, 2, 8], [2, 2, 2, 4], [2, 2, 2, 2, 2], [2, 4, 4], [4, 8]],
get_factors(32))
def test_get_factors_iterative1(self):
self.assertEqual([[2, 16], [4, 8], [2, 2, 8], [2, 4, 4], [2, 2, 2, 4], [2, 2, 2, 2, 2]],
get_factors_iterative1(32))
def test_get_factors_iterative2(self):
self.assertEqual([[2, 2, 2, 2, 2], [2, 2, 2, 4], [2, 2, 8], [2, 4, 4], [2, 16], [4, 8]],
get_factors_iterative2(32))
if __name__ == "__main__":
unittest.main()
7.2 count islands¶
Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Example 1:
11110 11010 11000 00000 Answer: 1
Example 2:
11000 11000 00100 00011 Answer: 3
def num_islands(grid):
count = 0
for i, row in enumerate(grid):
for j, col in enumerate(grid[i]):
if col == '1':
dfs(grid, i, j)
count += 1
return count
def dfs(grid, i, j):
if (i < 0 or i >= len(grid)) or (j < 0 or len(grid[0])):
return
if grid[i][j] != '1':
return
grid[i][j] = '0'
dfs(grid, i+1, j)
dfs(grid, i-1, j)
dfs(grid, i, j+1)
dfs(grid, i, j-1)
7.3 pacific atlantic¶
# Given an m x n matrix of non-negative integers representing # the height of each unit cell in a continent, # the "Pacific ocean" touches the left and top edges of the matrix # and the "Atlantic ocean" touches the right and bottom edges.
# Water can only flow in four directions (up, down, left, or right) # from a cell to another one with height equal or lower.
# Find the list of grid coordinates where water can flow to both the # Pacific and Atlantic ocean.
# Note: # The order of returned grid coordinates does not matter. # Both m and n are less than 150. # Example:
# Given the following 5x5 matrix:
- # Pacific ~ ~ ~ ~ ~
# ~ 1 2 2 3 (5) * # ~ 3 2 3 (4) (4) * # ~ 2 4 (5) 3 1 * # ~ (6) (7) 1 4 5 * # ~ (5) 1 1 2 4 *
# * * * * * Atlantic
# Return:
# [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] # (positions with parentheses in above matrix).
def pacific_atlantic(matrix):
"""
:type matrix: List[List[int]]
:rtype: List[List[int]]
"""
n = len(matrix)
if not n: return []
m = len(matrix[0])
if not m: return []
res = []
atlantic = [[False for _ in range (n)] for _ in range(m)]
pacific = [[False for _ in range (n)] for _ in range(m)]
for i in range(n):
dfs(pacific, matrix, float("-inf"), i, 0)
dfs(atlantic, matrix, float("-inf"), i, m-1)
for i in range(m):
dfs(pacific, matrix, float("-inf"), 0, i)
dfs(atlantic, matrix, float("-inf"), n-1, i)
for i in range(n):
for j in range(m):
if pacific[i][j] and atlantic[i][j]:
res.append([i, j])
return res
def dfs(grid, matrix, height, i, j):
if i < 0 or i >= len(matrix) or j < 0 or j >= len(matrix[0]):
return
if grid[i][j] or matrix[i][j] < height:
return
grid[i][j] = True
dfs(grid, matrix, matrix[i][j], i-1, j)
dfs(grid, matrix, matrix[i][j], i+1, j)
dfs(grid, matrix, matrix[i][j], i, j-1)
dfs(grid, matrix, matrix[i][j], i, j+1)
7.4 sudoku solver¶
It's similar to how human solve Sudoku.
create a hash table (dictionary) val to store possible values in every location. Each time, start from the location with fewest possible values, choose one value from it and then update the board and possible values at other locations. If this update is valid, keep solving (DFS). If this update is invalid (leaving zero possible values at some locations) or this value doesn't lead to the solution, undo the updates and then choose the next value. Since we calculated val at the beginning and start filling the board from the location with fewest possible values, the amount of calculation and thus the runtime can be significantly reduced:
The run time is 48-68 ms on LeetCode OJ, which seems to be among the fastest python solutions here.
The PossibleVals function may be further simplified/optimized, but it works just fine for now. (it would look less lengthy if we are allowed to use numpy array for the board lol).
import unittest
class Sudoku:
def __init__ (self, board, row, col):
self.board = board
self.row = row
self.col = col
self.val = self.possible_values()
def possible_values(self):
a = "123456789"
d, val = {}, {}
for i in range(self.row):
for j in range(self.col):
ele = self.board[i][j]
if ele != ".":
d[("r", i)] = d.get(("r", i), []) + [ele]
d[("c", j)] = d.get(("c", j), []) + [ele]
d[(i//3, j//3)] = d.get((i//3, j//3), []) + [ele]
else:
val[(i,j)] = []
for (i,j) in val.keys():
inval = d.get(("r",i),[])+d.get(("c",j),[])+d.get((i/3,j/3),[])
val[(i,j)] = [n for n in a if n not in inval ]
return val
def solve(self):
if len(self.val)==0:
return True
kee = min(self.val.keys(), key=lambda x: len(self.val[x]))
nums = self.val[kee]
for n in nums:
update = {kee:self.val[kee]}
if self.valid_one(n, kee, update): # valid choice
if self.solve(): # keep solving
return True
self.undo(kee, update) # invalid choice or didn't solve it => undo
return False
def valid_one(self, n, kee, update):
self.board[kee[0]][kee[1]] = n
del self.val[kee]
i, j = kee
for ind in self.val.keys():
if n in self.val[ind]:
if ind[0]==i or ind[1]==j or (ind[0]/3,ind[1]/3)==(i/3,j/3):
update[ind] = n
self.val[ind].remove(n)
if len(self.val[ind])==0:
return False
return True
def undo(self, kee, update):
self.board[kee[0]][kee[1]]="."
for k in update:
if k not in self.val:
self.val[k]= update[k]
else:
self.val[k].append(update[k])
return None
def __str__(self):
"""[summary]
Generates a board representation as string.
Returns:
[str] -- [board representation]
"""
resp = ""
for i in range(self.row):
for j in range(self.col):
resp += " {0} ".format(self.board[i][j])
resp += "\n"
return resp
class TestSudoku(unittest.TestCase):
def test_sudoku_solver(self):
board = [["5","3","."], ["6",".", "."],[".","9","8"]]
test_obj = Sudoku(board, 3, 3)
test_obj.solve()
self.assertEqual([['5', '3', '1'], ['6', '1', '2'], ['1', '9', '8']],test_obj.board)
if __name__ == "__main__":
unittest.main()
7.5 walls and getes¶
# fill the empty room with distance to its nearest gate
def walls_and_gates(rooms):
for i in range(len(rooms)):
for j in range(len(rooms[0])):
if rooms[i][j] == 0:
dfs(rooms, i, j, 0)
def dfs(rooms, i, j, depth):
if (i < 0 or i >= len(rooms)) or (j < 0 or j >= len(rooms[0])):
return # out of bounds
if rooms[i][j] < depth:
return # crossed
rooms[i][j] = depth
dfs(rooms, i+1, j, depth+1)
dfs(rooms, i-1, j, depth+1)
dfs(rooms, i, j+1, depth+1)
dfs(rooms, i, j-1, depth+1)
chapter 8: Dynamic Programming¶
8.1 buy sell stock¶
Say you have an array for which the ith element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.
Example 1: Input: [7, 1, 5, 3, 6, 4] Output: 5
max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be larger than buying price) Example 2: Input: [7, 6, 4, 3, 1] Output: 0
In this case, no transaction is done, i.e. max profit = 0.
# O(n^2) time
def max_profit_naive(prices):
"""
:type prices: List[int]
:rtype: int
"""
max_so_far = 0
for i in range(0, len(prices) - 1):
for j in range(i + 1, len(prices)):
max_so_far = max(max_so_far, prices[j] - prices[i])
return max_so_far
# O(n) time
def max_profit_optimized(prices):
"""
input: [7, 1, 5, 3, 6, 4]
diff : [X, -6, 4, -2, 3, -2]
:type prices: List[int]
:rtype: int
"""
cur_max, max_so_far = 0, 0
for i in range(1, len(prices)):
cur_max = max(0, cur_max + prices[i] - prices[i-1])
max_so_far = max(max_so_far, cur_max)
return max_so_far
8.2 climbing stairs¶
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Note: Given n will be a positive integer.
# O(n) space
def climb_stairs(n):
"""
:type n: int
:rtype: int
"""
arr = [1, 1]
for i in range(2, n+1):
arr.append(arr[-1] + arr[-2])
return arr[-1]
# the above function can be optimized as:
# O(1) space
def climb_stairs_optimized(n):
a = b = 1
for _ in range(n):
a, b = b, a + b
return a
8.3 coin change¶
Problem Given a value N, if we want to make change for N cents, and we have infinite supply of each of S = { S1, S2, .. , Sm} valued //coins, how many ways can we make the change? The order of coins doesn’t matter. For example, for N = 4 and S = [1, 2, 3], there are four solutions: [1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3]. So output should be 4.
For N = 10 and S = [2, 5, 3, 6], there are five solutions: [2, 2, 2, 2, 2], [2, 2, 3, 3], [2, 2, 6], [2, 3, 5] and [5, 5]. So the output should be 5.
def count(s, n):
# We need n+1 rows as the table is consturcted in bottom up
# manner using the base case 0 value case (n = 0)
m = len(s)
table = [[0 for x in range(m)] for x in range(n+1)]
# Fill the enteries for 0 value case (n = 0)
for i in range(m):
table[0][i] = 1
# Fill rest of the table enteries in bottom up manner
for i in range(1, n+1):
for j in range(m):
# Count of solutions including S[j]
x = table[i - s[j]][j] if i-s[j] >= 0 else 0
# Count of solutions excluding S[j]
y = table[i][j-1] if j >= 1 else 0
# total count
table[i][j] = x + y
return table[n][m-1]
if __name__ == '__main__':
coins = [1, 2, 3]
n = 4
assert count(coins, n) == 4
coins = [2, 5, 3, 6]
n = 10
assert count(coins, n) == 5
8.4 combination sum¶
Given an integer array with all positive numbers and no duplicates, find the number of possible combinations that add up to a positive integer target.
Example:
nums = [1, 2, 3] target = 4
The possible combination ways are: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)
Note that different sequences are counted as different combinations.
Therefore the output is 7. Follow up: What if negative numbers are allowed in the given array? How does it change the problem? What limitation we need to add to the question to allow negative numbers?
dp = None
def helper_topdown(nums, target):
global dp
if dp[target] != -1:
return dp[target]
res = 0
for i in range(0, len(nums)):
if target >= nums[i]:
res += helper_topdown(nums, target - nums[i])
dp[target] = res
return res
def combination_sum_topdown(nums, target):
global dp
dp = [-1] * (target + 1)
dp[0] = 1
return helper_topdown(nums, target)
# EDIT: The above solution is top-down. How about a bottom-up one?
def combination_sum_bottom_up(nums, target):
comb = [0] * (target + 1)
comb[0] = 1
for i in range(0, len(comb)):
for j in range(len(nums)):
if i - nums[j] >= 0:
comb[i] += comb[i - nums[j]]
return comb[target]
combination_sum_topdown([1, 2, 3], 4)
print(dp[4])
print(combination_sum_bottom_up([1, 2, 3], 4))
8.5 edit distance¶
The edit distance between two words is the minimum number of letter insertions, letter deletions, and letter substitutions required to transform one word into another.
For example, the edit distance between FOOD and MONEY is at most four:
FOOD -> MOOD -> MOND -> MONED -> MONEY
Given two words A and B, find the minimum number of operations required to transform one string into the other. In other words, find the edit distance between A and B.
Thought process:
Let edit(i, j) denote the edit distance between the prefixes A[1..i] and B[1..j].
Then, the function satifies the following recurrence:
- edit(i, j) = i if j = 0
j if i = 0 min(edit(i-1, j) + 1,
edit(i, j-1), + 1, edit(i-1, j-1) + cost) otherwise
There are two base cases, both of which occur when one string is empty and the other is not. 1. To convert an empty string A into a string B of length n, perform n insertions. 2. To convert a string A of length m into an empty string B, perform m deletions.
Here, the cost is 1 if a substitution is required, or 0 if both chars in words A and B are the same at indexes i and j, respectively.
To find the edit distance between two words A and B, we need to find edit(m, n), where m is the length of A and n is the length of B.
def edit_distance(A, B):
# Time: O(m*n)
# Space: O(m*n)
m, n = len(A) + 1, len(B) + 1
edit = [[0 for _ in range(n)] for _ in range(m)]
for i in range(1, m):
edit[i][0] = i
for j in range(1, n):
edit[0][j] = j
for i in range(1, m):
for j in range(1, n):
cost = 0 if A[i - 1] == B[j - 1] else 1
edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + cost)
return edit[-1][-1] # this is the same as edit[m][n]
8.6 egg drop¶
# A Dynamic Programming based Python Program for the Egg Dropping Puzzle INT_MAX = 32767
# Function to get minimum number of trials needed in worst # case with n eggs and k floors
def egg_drop(n, k):
# A 2D table where entery eggFloor[i][j] will represent minimum
# number of trials needed for i eggs and j floors.
egg_floor = [[0 for x in range(k+1)] for x in range(n+1)]
# We need one trial for one floor and0 trials for 0 floors
for i in range(1, n+1):
egg_floor[i][1] = 1
egg_floor[i][0] = 0
# We always need j trials for one egg and j floors.
for j in range(1, k+1):
egg_floor[1][j] = j
# Fill rest of the entries in table using optimal substructure
# property
for i in range(2, n+1):
for j in range(2, k+1):
egg_floor[i][j] = INT_MAX
for x in range(1, j+1):
res = 1 + max(egg_floor[i-1][x-1], egg_floor[i][j-x])
if res < egg_floor[i][j]:
egg_floor[i][j] = res
# eggFloor[n][k] holds the result
return egg_floor[n][k]
8.7 fib¶
def fib_recursive(n):
"""[summary]
Computes the n-th fibonacci number recursive.
Problem: This implementation is very slow.
approximate O(2^n)
Arguments:
n {[int]} -- [description]
Returns:
[int] -- [description]
"""
# precondition
assert n >= 0, 'n must be a positive integer'
if n <= 1:
return n
else:
return fib_recursive(n-1) + fib_recursive(n-2)
# print(fib_recursive(35)) # => 9227465 (slow)
def fib_list(n):
"""[summary]
This algorithm computes the n-th fibbonacci number
very quick. approximate O(n)
The algorithm use dynamic programming.
Arguments:
n {[int]} -- [description]
Returns:
[int] -- [description]
"""
# precondition
assert n >= 0, 'n must be a positive integer'
list_results = [0, 1]
for i in range(2, n+1):
list_results.append(list_results[i-1] + list_results[i-2])
return list_results[n]
# print(fib_list(100)) # => 354224848179261915075
def fib_iter(n):
"""[summary]
Works iterative approximate O(n)
Arguments:
n {[int]} -- [description]
Returns:
[int] -- [description]
"""
# precondition
assert n >= 0, 'n must be positive integer'
fib_1 = 0
fib_2 = 1
sum = 0
if n <= 1:
return n
for i in range(n-1):
sum = fib_1 + fib_2
fib_1 = fib_2
fib_2 = sum
return sum
# => 354224848179261915075
# print(fib_iter(100))
8.8 hosoya triangle¶
Hosoya triangle (originally Fibonacci triangle) is a triangular arrangement of numbers, where if you take any number it is the sum of 2 numbers above. First line is always 1, and second line is always {1 1}.
This printHosoya function takes argument n which is the height of the triangle (number of lines).
For example: printHosoya( 6 ) would return: 1 1 1 2 1 2 3 2 2 3 5 3 4 3 5 8 5 6 6 5 8
The complexity is O(n^3).
def hosoya(n, m):
if ((n == 0 and m == 0) or (n == 1 and m == 0) or
(n == 1 and m == 1) or (n == 2 and m == 1)):
return 1
if n > m:
return hosoya(n - 1, m) + hosoya(n - 2, m)
elif m == n:
return hosoya(n - 1, m - 1) + hosoya(n - 2, m - 2)
else:
return 0
def print_hosoya(n):
for i in range(n):
for j in range(i + 1):
print(hosoya(i, j) , end = " ")
print ("\n", end = "")
def hosoya_testing(n):
x = []
for i in range(n):
for j in range(i + 1):
x.append(hosoya(i, j))
return x
8.9 house robber¶
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
def house_robber(houses):
last, now = 0, 0
for house in houses:
tmp = now
now = max(last + house, now)
last = tmp
return now
houses = [1, 2, 16, 3, 15, 3, 12, 1]
print(house_robber(houses))
8.10 job scheduling¶
# Python program for weighted job scheduling using Dynamic
# Programming and Binary Search
# Class to represent a job
class Job:
def __init__(self, start, finish, profit):
self.start = start
self.finish = finish
self.profit = profit
# A Binary Search based function to find the latest job
# (before current job) that doesn't conflict with current
# job. "index" is index of the current job. This function
# returns -1 if all jobs before index conflict with it.
# The array jobs[] is sorted in increasing order of finish
# time.
def binary_search(job, start_index):
# Initialize 'lo' and 'hi' for Binary Search
lo = 0
hi = start_index - 1
# Perform binary Search iteratively
while lo <= hi:
mid = (lo + hi) // 2
if job[mid].finish <= job[start_index].start:
if job[mid + 1].finish <= job[start_index].start:
lo = mid + 1
else:
return mid
else:
hi = mid - 1
return -1
# The main function that returns the maximum possible
# profit from given array of jobs
def schedule(job):
# Sort jobs according to finish time
job = sorted(job, key = lambda j: j.finish)
# Create an array to store solutions of subproblems. table[i]
# stores the profit for jobs till arr[i] (including arr[i])
n = len(job)
table = [0 for _ in range(n)]
table[0] = job[0].profit
# Fill entries in table[] using recursive property
for i in range(1, n):
# Find profit including the current job
incl_prof = job[i].profit
l = binary_search(job, i)
if (l != -1):
incl_prof += table[l]
# Store maximum of including and excluding
table[i] = max(incl_prof, table[i - 1])
return table[n-1]
8.11 knapsack¶
Given the capacity of the knapsack and items specified by weights and values, return the maximum summarized value of the items that can be fit in the knapsack.
Example: capacity = 5, items(value, weight) = [(60, 5), (50, 3), (70, 4), (30, 2)] result = 80 (items valued 50 and 30 can both be fit in the knapsack)
The time complexity is O(n * m) and the space complexity is O(m), where n is the total number of items and m is the knapsack's capacity.
class Item(object):
def __init__(self, value, weight):
self.value = value
self.weight = weight
def get_maximum_value(items, capacity):
dp = [0] * (capacity + 1)
for item in items:
dp_tmp = [total_value for total_value in dp]
for current_weight in range(capacity + 1):
total_weight = current_weight + item.weight
if total_weight <= capacity:
dp_tmp[total_weight] = max(dp_tmp[total_weight],
dp[current_weight] + item.value)
dp = dp_tmp
return max(dp)
print(get_maximum_value([Item(60, 10), Item(100, 20), Item(120, 30)],
50))
print(get_maximum_value([Item(60, 5), Item(50, 3), Item(70, 4), Item(30, 2)],
5))
8.12 longest increasing¶
def longest_increasing_subsequence(sequence):
"""
Dynamic Programming Algorithm for
counting the length of longest increasing subsequence
type sequence: List[int]
"""
length = len(sequence)
counts = [1 for _ in range(length)]
for i in range(1, length):
for j in range(0, i):
if sequence[i] > sequence[j]:
counts[i] = max(counts[i], counts[j] + 1)
print(counts)
return max(counts)
sequence = [1, 101, 10, 2, 3, 100, 4, 6, 2]
print("sequence: ", sequence)
print("output: ", longest_increasing_subsequence(sequence))
print("answer: ", 5)
8.13 matrix chain order¶
Dynamic Programming Implementation of matrix Chain Multiplication Time Complexity: O(n^3) Space Complexity: O(n^2)
INF = float("inf")
def matrix_chain_order(array):
n=len(array)
matrix = [[0 for x in range(n)] for x in range(n)]
sol = [[0 for x in range(n)] for x in range(n)]
for chain_length in range(2,n):
for a in range(1,n-chain_length+1):
b = a+chain_length-1
matrix[a][b] = INF
for c in range(a, b):
cost = matrix[a][c] + matrix[c+1][b] + array[a-1]*array[c]*array[b]
if cost < matrix[a][b]:
matrix[a][b] = cost
sol[a][b] = c
return matrix , sol
#Print order of matrix with Ai as matrix
def print_optimal_solution(optimal_solution,i,j):
if i==j:
print("A" + str(i),end = " ")
else:
print("(",end = " ")
print_optimal_solution(optimal_solution,i,optimal_solution[i][j])
print_optimal_solution(optimal_solution,optimal_solution[i][j]+1,j)
print(")",end = " ")
def main():
array=[30,35,15,5,10,20,25]
n=len(array)
#Size of matrix created from above array will be
# 30*35 35*15 15*5 5*10 10*20 20*25
matrix , optimal_solution = matrix_chain_order(array)
print("No. of Operation required: "+str((matrix[1][n-1])))
print_optimal_solution(optimal_solution,1,n-1)
if __name__ == '__main__':
main()
8.14 max product subarray¶
Find the contiguous subarray within an array (containing at least one number) which has the largest product.
For example, given the array [2,3,-2,4], the contiguous subarray [2,3] has the largest product = 6.
from functools import reduce
def max_product(nums):
"""
:type nums: List[int]
:rtype: int
"""
lmin = lmax = gmax = nums[0]
for i in range(len(nums)):
t1 = nums[i] * lmax
t2 = nums[i] * lmin
lmax = max(max(t1, t2), nums[i])
lmin = min(min(t1, t2), nums[i])
gmax = max(gmax, lmax)
Another approach that would print max product and the subarray
Examples: subarray_with_max_product([2,3,6,-1,-1,9,5])
#=> max_product_so_far: 45, [-1, -1, 9, 5]
- subarray_with_max_product([-2,-3,6,0,-7,-5])
- #=> max_product_so_far: 36, [-2, -3, 6]
- subarray_with_max_product([-4,-3,-2,-1])
- #=> max_product_so_far: 24, [-4, -3, -2, -1]
- subarray_with_max_product([-3,0,1])
- #=> max_product_so_far: 1, [1]
def subarray_with_max_product(arr):
''' arr is list of positive/negative numbers '''
l = len(arr)
product_so_far = max_product_end = 1
max_start_i = 0
so_far_start_i = so_far_end_i = 0
all_negative_flag = True
for i in range(l):
max_product_end *= arr[i]
if arr[i] > 0:
all_negative_flag = False
if max_product_end <= 0:
max_product_end = arr[i]
max_start_i = i
if product_so_far <= max_product_end:
product_so_far = max_product_end
so_far_end_i = i
so_far_start_i = max_start_i
if all_negative_flag:
print("max_product_so_far: %s, %s" %
(reduce(lambda x, y: x * y, arr), arr))
else:
print("max_product_so_far: %s, %s" %
(product_so_far, arr[so_far_start_i:so_far_end_i + 1]))
8.15 max subarray¶
def max_subarray(array):
max_so_far = max_now = array[0]
for i in range(1, len(array)):
max_now = max(array[i], max_now + array[i])
max_so_far = max(max_so_far, max_now)
return max_so_far
a = [1, 2, -3, 4, 5, -7, 23]
print(a)
print(max_subarray(a))
8.16 min cost path¶
author @goswami-rahul
To find minimum cost path from station 0 to station N-1, where cost of moving from ith station to jth station is given as:
Matrix of size (N x N) where Matrix[i][j] denotes the cost of moving from station i --> station j for i < j
NOTE that values where Matrix[i][j] and i > j does not mean anything, and hence represented by -1 or INF
For the input below (cost matrix), Minimum cost is obtained as from { 0 --> 1 --> 3}
= cost[0][1] + cost[1][3] = 65
the Output will be:
The Minimum cost to reach station 4 is 65
Time Complexity: O(n^2) Space Complexity: O(n)
INF = float("inf")
def min_cost(cost):
n = len(cost)
# dist[i] stores minimum cost from 0 --> i.
dist = [INF] * n
dist[0] = 0 # cost from 0 --> 0 is zero.
for i in range(n):
for j in range(i+1,n):
dist[j] = min(dist[j], dist[i] + cost[i][j])
return dist[n-1]
if __name__ == '__main__':
cost = [ [ 0, 15, 80, 90], # cost[i][j] is the cost of
[-1, 0, 40, 50], # going from i --> j
[-1, -1, 0, 70],
[-1, -1, -1, 0] ] # cost[i][j] = -1 for i > j
total_len = len(cost)
mcost = min_cost(cost)
assert mcost == 65
print("The Minimum cost to reach station %d is %d" % (total_len, mcost))
8.17 num decodings¶
A message containing letters from A-Z is being encoded to numbers using the following mapping:
'A' -> 1 'B' -> 2 ... 'Z' -> 26 Given an encoded message containing digits, determine the total number of ways to decode it.
For example, Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).
The number of ways decoding "12" is 2.
def num_decodings(s):
"""
:type s: str
:rtype: int
"""
if not s or s[0] == "0":
return 0
wo_last, wo_last_two = 1, 1
for i in range(1, len(s)):
x = wo_last if s[i] != "0" else 0
y = wo_last_two if int(s[i-1:i+1]) < 27 and s[i-1] != "0" else 0
wo_last_two = wo_last
wo_last = x+y
return wo_last
def num_decodings2(s):
if not s or s.startswith('0'):
return 0
stack = [1, 1]
for i in range(1, len(s)):
if s[i] == '0':
if s[i-1] == '0' or s[i-1] > '2':
# only '10', '20' is valid
return 0
stack.append(stack[-2])
elif 9 < int(s[i-1:i+1]) < 27:
# '01 - 09' is not allowed
stack.append(stack[-2]+stack[-1])
else:
# other case '01, 09, 27'
stack.append(stack[-1])
return stack[-1]
8.18 regex matching¶
Implement regular expression matching with support for '.' and '*'.
'.' Matches any single character. '*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
The function prototype should be: bool isMatch(const char *s, const char *p)
Some examples: isMatch("aa","a") → false isMatch("aa","aa") → true isMatch("aaa","aa") → false isMatch("aa", "a*") → true isMatch("aa", ".*") → true isMatch("ab", ".*") → true isMatch("aab", "c*a*b") → true
import unittest
class Solution(object):
def is_match(self, s, p):
m, n = len(s) + 1, len(p) + 1
matches = [[False] * n for _ in range(m)]
# Match empty string with empty pattern
matches[0][0] = True
# Match empty string with .*
for i, element in enumerate(p[1:], 2):
matches[0][i] = matches[0][i - 2] and element == '*'
for i, ss in enumerate(s, 1):
for j, pp in enumerate(p, 1):
if pp != '*':
# The previous character has matched and the current one
# has to be matched. Two possible matches: the same or .
matches[i][j] = matches[i - 1][j - 1] and \
(ss == pp or pp == '.')
else:
# Horizontal look up [j - 2].
# Not use the character before *.
matches[i][j] |= matches[i][j - 2]
# Vertical look up [i - 1].
# Use at least one character before *.
# p a b *
# s 1 0 0 0
# a 0 1 0 1
# b 0 0 1 1
# b 0 0 0 ?
if ss == p[j - 2] or p[j - 2] == '.':
matches[i][j] |= matches[i - 1][j]
return matches[-1][-1]
class TestSolution(unittest.TestCase):
def test_none_0(self):
s = ""
p = ""
self.assertTrue(Solution().isMatch(s, p))
def test_none_1(self):
s = ""
p = "a"
self.assertFalse(Solution().isMatch(s, p))
def test_no_symbol_equal(self):
s = "abcd"
p = "abcd"
self.assertTrue(Solution().isMatch(s, p))
def test_no_symbol_not_equal_0(self):
s = "abcd"
p = "efgh"
self.assertFalse(Solution().isMatch(s, p))
def test_no_symbol_not_equal_1(self):
s = "ab"
p = "abb"
self.assertFalse(Solution().isMatch(s, p))
def test_symbol_0(self):
s = ""
p = "a*"
self.assertTrue(Solution().isMatch(s, p))
def test_symbol_1(self):
s = "a"
p = "ab*"
self.assertTrue(Solution().isMatch(s, p))
def test_symbol_2(self):
# E.g.
# s a b b
# p 1 0 0 0
# a 0 1 0 0
# b 0 0 1 0
# * 0 1 1 1
s = "abb"
p = "ab*"
self.assertTrue(Solution().isMatch(s, p))
if __name__ == "__main__":
unittest.main()
8.19 rod cut¶
# A Dynamic Programming solution for Rod cutting problem
INT_MIN = -32767
# Returns the best obtainable price for a rod of length n and
# price[] as prices of different pieces
def cut_rod(price):
n = len(price)
val = [0]*(n+1)
# Build the table val[] in bottom up manner and return
# the last entry from the table
for i in range(1, n+1):
max_val = INT_MIN
for j in range(i):
max_val = max(max_val, price[j] + val[i-j-1])
val[i] = max_val
return val[n]
# Driver program to test above functions
arr = [1, 5, 8, 9, 10, 17, 17, 20]
print("Maximum Obtainable Value is " + str(cut_rod(arr)))
# This code is contributed by Bhavya Jain
8.20 word break¶
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words. You may assume the dictionary does not contain duplicate words.
For example, given s = "leetcode", dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".
s = abc word_dict = ["a","bc"] True False False False
# TC: O(N^2) SC: O(N)
def word_break(s, word_dict):
"""
:type s: str
:type word_dict: Set[str]
:rtype: bool
"""
dp = [False] * (len(s)+1)
dp[0] = True
for i in range(1, len(s)+1):
for j in range(0, i):
if dp[j] and s[j:i] in word_dict:
dp[i] = True
break
return dp[-1]
if __name__ == "__main__":
s = "keonkim"
dic = ["keon", "kim"]
print(word_break(s, dic))
chapter 9: graph¶
9.1 check bipartite¶
Bipartite graph is a graph whose vertices can be divided into two disjoint and independent sets. (https://en.wikipedia.org/wiki/Bipartite_graph)
Time complexity is O(|E|) Space complexity is O(|V|)
def check_bipartite(adj_list):
V = len(adj_list)
# Divide vertexes in the graph into set_type 1 and 2
# Initialize all set_types as -1
set_type = [-1 for v in range(V)]
set_type[0] = 0
q = [0]
while q:
v = q.pop(0)
# If there is a self-loop, it cannot be bipartite
if adj_list[v][v]:
return False
for u in range(V):
if adj_list[v][u]:
if set_type[u] == set_type[v]:
return False
elif set_type[u] == -1:
# set type of u opposite of v
set_type[u] = 1 - set_type[v]
q.append(u)
return True
9.2 check digraph strongly connected¶
from collections import defaultdict
class Graph:
def __init__(self,v):
self.v = v
self.graph = defaultdict(list)
def add_edge(self,u,v):
self.graph[u].append(v)
def dfs(self):
visited = [False] * self.v
self.dfs_util(0,visited)
if visited == [True]*self.v:
return True
return False
def dfs_util(self,i,visited):
visited[i] = True
for u in self.graph[i]:
if not(visited[u]):
self.dfs_util(u,visited)
def reverse_graph(self):
g = Graph(self.v)
for i in range(len(self.graph)):
for j in self.graph[i]:
g.add_edge(j,i)
return g
def is_sc(self):
if self.dfs():
gr = self.reverse_graph()
if gr.dfs():
return True
return False
g1 = Graph(5)
g1.add_edge(0, 1)
g1.add_edge(1, 2)
g1.add_edge(2, 3)
g1.add_edge(3, 0)
g1.add_edge(2, 4)
g1.add_edge(4, 2)
print ("Yes") if g1.is_sc() else print("No")
g2 = Graph(4)
g2.add_edge(0, 1)
g2.add_edge(1, 2)
g2.add_edge(2, 3)
print ("Yes") if g2.is_sc() else print("No")
9.3 clone graph¶
Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.
OJ's undirected graph serialization: Nodes are labeled uniquely.
We use # as a separator for each node, and , as a separator for node label and each neighbor of the node. As an example, consider the serialized graph {0,1,2#1,2#2,2}.
The graph has a total of three nodes, and therefore contains three parts as separated by #.
First node is labeled as 0. Connect node 0 to both nodes 1 and 2. Second node is labeled as 1. Connect node 1 to node 2. Third node is labeled as 2. Connect node 2 to node 2 (itself), thus forming a self-cycle. Visually, the graph looks like the following:
1/
/
- 0 --- 2
- / _/
import collections
# Definition for a undirected graph node
class UndirectedGraphNode:
def __init__(self, x):
self.label = x
self.neighbors = []
# BFS
def clone_graph1(node):
if not node:
return
node_copy = UndirectedGraphNode(node.label)
dic = {node: node_copy}
queue = collections.deque([node])
while queue:
node = queue.popleft()
for neighbor in node.neighbors:
if neighbor not in dic: # neighbor is not visited
neighbor_copy = UndirectedGraphNode(neighbor.label)
dic[neighbor] = neighbor_copy
dic[node].neighbors.append(neighbor_copy)
queue.append(neighbor)
else:
dic[node].neighbors.append(dic[neighbor])
return node_copy
# DFS iteratively
def clone_graph2(node):
if not node:
return
node_copy = UndirectedGraphNode(node.label)
dic = {node: node_copy}
stack = [node]
while stack:
node = stack.pop()
for neighbor in node.neighbors:
if neighbor not in dic:
neighbor_copy = UndirectedGraphNode(neighbor.label)
dic[neighbor] = neighbor_copy
dic[node].neighbors.append(neighbor_copy)
stack.append(neighbor)
else:
dic[node].neighbors.append(dic[neighbor])
return node_copy
# DFS recursively
def clone_graph(node):
if not node:
return
node_copy = UndirectedGraphNode(node.label)
dic = {node: node_copy}
dfs(node, dic)
return node_copy
def dfs(node, dic):
for neighbor in node.neighbors:
if neighbor not in dic:
neighbor_copy = UndirectedGraphNode(neighbor.label)
dic[neighbor] = neighbor_copy
dic[node].neighbors.append(neighbor_copy)
dfs(neighbor, dic)
else:
dic[node].neighbors.append(dic[neighbor])
9.4 cycle detection¶
Given a directed graph, check whether it contains a cycle.
Real-life scenario: deadlock detection in a system. Processes may be represented by vertices, then and an edge A -> B could mean that process A is waiting for B to release its lock on a resource.
from enum import Enum
class TraversalState(Enum):
WHITE = 0
GRAY = 1
BLACK = 2
example_graph_with_cycle = {'A': ['B', 'C'],
'B': ['D'],
'C': ['F'],
'D': ['E', 'F'],
'E': ['B'],
'F': []}
example_graph_without_cycle = {'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': ['E'],
'E': [],
'F': []}
def is_in_cycle(graph, traversal_states, vertex):
if traversal_states[vertex] == TraversalState.GRAY:
return True
traversal_states[vertex] = TraversalState.GRAY
for neighbor in graph[vertex]:
if is_in_cycle(graph, traversal_states, neighbor):
return True
traversal_states[vertex] = TraversalState.BLACK
return False
def contains_cycle(graph):
traversal_states = {vertex: TraversalState.WHITE for vertex in graph}
for vertex, state in traversal_states.items():
if (state == TraversalState.WHITE and
is_in_cycle(graph, traversal_states, vertex)):
return True
return False
print(contains_cycle(example_graph_with_cycle))
print(contains_cycle(example_graph_without_cycle))
9.5 dijkstra¶
#Dijkstra's single source shortest path algorithm
class Graph():
def __init__(self, vertices):
self.vertices = vertices
self.graph = [[0 for column in range(vertices)] for row in range(vertices)]
def min_distance(self, dist, min_dist_set):
min_dist = float("inf")
for v in range(self.vertices):
if dist[v] < min_dist and min_dist_set[v] == False:
min_dist = dist[v]
min_index = v
return min_index
def dijkstra(self, src):
dist = [float("inf")] * self.vertices
dist[src] = 0
min_dist_set = [False] * self.vertices
for count in range(self.vertices):
#minimum distance vertex that is not processed
u = self.min_distance(dist, min_dist_set)
#put minimum distance vertex in shortest tree
min_dist_set[u] = True
#Update dist value of the adjacent vertices
for v in range(self.vertices):
if self.graph[u][v] > 0 and min_dist_set[v] == False and dist[v] > dist[u] + self.graph[u][v]:
dist[v] = dist[u] + self.graph[u][v]
return dist
9.6 find all cliques¶
# takes dict of sets # each key is a vertex # value is set of all edges connected to vertex # returns list of lists (each sub list is a maximal clique) # implementation of the basic algorithm described in: # Bron, Coen; Kerbosch, Joep (1973), "Algorithm 457: finding all cliques of an undirected graph",
def find_all_cliques(edges):
def expand_clique(candidates, nays):
nonlocal compsub
if not candidates and not nays:
nonlocal solutions
solutions.append(compsub.copy())
else:
for selected in candidates.copy():
candidates.remove(selected)
candidates_temp = get_connected(selected, candidates)
nays_temp = get_connected(selected, nays)
compsub.append(selected)
expand_clique(candidates_temp, nays_temp)
nays.add(compsub.pop())
def get_connected(vertex, old_set):
new_set = set()
for neighbor in edges[str(vertex)]:
if neighbor in old_set:
new_set.add(neighbor)
return new_set
compsub = []
solutions = []
possibles = set(edges.keys())
expand_clique(possibles, set())
return solutions
9.7 find path¶
myGraph = {'A': ['B', 'C'],
'B': ['C', 'D'],
'C': ['D', 'F'],
'D': ['C'],
'E': ['F'],
'F': ['C']}
# find path from start to end using recursion with backtracking
def find_path(graph, start, end, path=[]):
path = path + [start]
if (start == end):
return path
if not start in graph:
return None
for node in graph[start]:
if node not in path:
newpath = find_path(graph, node, end, path)
return newpath
return None
# find all path
def find_all_path(graph, start, end, path=[]):
path = path + [start]
print(path)
if (start == end):
return [path]
if not start in graph:
return None
paths = []
for node in graph[start]:
if node not in path:
newpaths = find_all_path(graph, node, end, path)
for newpath in newpaths:
paths.append(newpath)
return paths
def find_shortest_path(graph, start, end, path=[]):
path = path + [start]
if start == end:
return path
if start not in graph:
return None
shortest = None
for node in graph[start]:
if node not in path:
newpath = find_shortest_path(graph, node, end, path)
if newpath:
if not shortest or len(newpath) < len(shortest):
shortest = newpath
return shortest
print(find_all_path(myGraph, 'A', 'F'))
# print(find_shortest_path(myGraph, 'A', 'D'))
9.8 graph¶
These are classes to represent a Graph and its elements. It can be shared across graph algorithms.
class Node(object):
def __init__(self, name):
self.name = name
@staticmethod
def get_name(obj):
if isinstance(obj, Node):
return obj.name
elif isinstance(obj, str):
return obj
return''
def __eq__(self, obj):
return self.name == self.get_name(obj)
def __repr__(self):
return self.name
def __hash__(self):
return hash(self.name)
def __ne__(self, obj):
return self.name != self.get_name(obj)
def __lt__(self, obj):
return self.name < self.get_name(obj)
def __le__(self, obj):
return self.name <= self.get_name(obj)
def __gt__(self, obj):
return self.name > self.get_name(obj)
def __ge__(self, obj):
return self.name >= self.get_name(obj)
def __bool__(self):
return self.name
class DirectedEdge(object):
def __init__(self, node_from, node_to):
self.nf = node_from
self.nt = node_to
def __eq__(self, obj):
if isinstance(obj, DirectedEdge):
return obj.nf == self.nf and obj.nt == self.nt
return False
def __repr__(self):
return '({0} -> {1})'.format(self.nf, self.nt)
class DirectedGraph(object):
def __init__(self, load_dict={}):
self.nodes = []
self.edges = []
self.adjmt = {}
if load_dict and type(load_dict) == dict:
for v in load_dict:
node_from = self.add_node(v)
self.adjmt[node_from] = []
for w in load_dict[v]:
node_to = self.add_node(w)
self.adjmt[node_from].append(node_to)
self.add_edge(v, w)
def add_node(self, node_name):
try:
return self.nodes[self.nodes.index(node_name)]
except ValueError:
node = Node(node_name)
self.nodes.append(node)
return node
def add_edge(self, node_name_from, node_name_to):
try:
node_from = self.nodes[self.nodes.index(node_name_from)]
node_to = self.nodes[self.nodes.index(node_name_to)]
self.edges.append(DirectedEdge(node_from, node_to))
except ValueError:
pass
class Graph:
def __init__(self, vertices):
# No. of vertices
self.V = vertices
# default dictionary to store graph
self.graph = {}
# To store transitive closure
self.tc = [[0 for j in range(self.V)] for i in range(self.V)]
# function to add an edge to graph
def add_edge(self, u, v):
if u in self.graph:
self.graph[u].append(v)
else:
self.graph[u] = [v]
#g = Graph(4)
#g.add_edge(0, 1)
#g.add_edge(0, 2)
#g.add_edge(1, 2)
#g.add_edge(2, 0)
#g.add_edge(2, 3)
#g.add_edge(3, 3)
9.9 markov chain¶
import random
my_chain = {
'A': {'A': 0.6,
'E': 0.4},
'E': {'A': 0.7,
'E': 0.3}
}
def __choose_state(state_map):
choice = random.random()
probability_reached = 0
for state, probability in state_map.items():
probability_reached += probability
if probability_reached > choice:
return state
def next_state(chain, current_state):
next_state_map = chain.get(current_state)
next_state = __choose_state(next_state_map)
return next_state
def iterating_markov_chain(chain, state):
while True:
state = next_state(chain, state)
yield state
9.10 minimum spanning tree¶
# Minimum spanning tree (MST) is going to use an undirected graph # # The disjoint set is represented with an list <n> of integers where # <n[i]> is the parent of the node at position <i>. # If <n[i]> = <i>, <i> it's a root, or a head, of a set
class Edge:
def __init__(self, u, v, weight):
self.u = u
self.v = v
self.weight = weight
class DisjointSet:
def __init__(self, n):
# Args:
# n (int): Number of vertices in the graph
self.parent = [None] * n # Contains wich node is the parent of the node at poisition <i>
self.size = [1] * n # Contains size of node at index <i>, used to optimize merge
for i in range(n):
self.parent[i] = i # Make all nodes his own parent, creating n sets.
def merge_set(self, a, b):
# Args:
# a, b (int): Indexes of nodes whose sets will be merged.
# Get the set of nodes at position <a> and <b>
# If <a> and <b> are the roots, this will be constant O(1)
a = self.find_set(a)
b = self.find_set(b)
# Join the shortest node to the longest, minimizing tree size (faster find)
if self.size[a] < self.size[b]:
self.parent[a] = b # Merge set(a) and set(b)
self.size[b] += self.size[a] # Add size of old set(a) to set(b)
else:
self.parent[b] = a # Merge set(b) and set(a)
self.size[a] += self.size[b] # Add size of old set(b) to set(a)
def find_set(self, a):
if self.parent[a] != a:
# Very important, memoize result of the
# recursion in the list to optimize next
# calls and make this operation practically constant, O(1)
self.parent[a] = self.find_set(self.parent[a])
# node <a> it's the set root, so we can return that index
return self.parent[a]
def kruskal(n, edges, ds):
# Args:
# n (int): Number of vertices in the graph
# edges (list of Edge): Edges of the graph
# ds (DisjointSet): DisjointSet of the vertices
# Returns:
# int: sum of weights of the minnimum spanning tree
#
# Kruskal algorithm:
# This algorithm will find the optimal graph with less edges and less
# total weight to connect all vertices (MST), the MST will always contain
# n-1 edges because it's the minimum required to connect n vertices.
#
# Procedure:
# Sort the edges (criteria: less weight).
# Only take edges of nodes in different sets.
# If we take a edge, we need to merge the sets to discard these.
# After repeat this until select n-1 edges, we will have the complete MST.
edges.sort(key=lambda edge: edge.weight)
mst = [] # List of edges taken, minimum spanning tree
for edge in edges:
set_u = ds.find_set(edge.u) # Set of the node <u>
set_v = ds.find_set(edge.v) # Set of the node <v>
if set_u != set_v:
ds.merge_set(set_u, set_v)
mst.append(edge)
if len(mst) == n-1:
# If we have selected n-1 edges, all the other
# edges will be discarted, so, we can stop here
break
return sum([edge.weight for edge in mst])
if __name__ == "__main__":
# Test. How input works:
# Input consists of different weighted, connected, undirected graphs.
# line 1:
# integers n, m
# lines 2..m+2:
# edge with the format -> node index u, node index v, integer weight
#
# Samples of input:
#
# 5 6
# 1 2 3
# 1 3 8
# 2 4 5
# 3 4 2
# 3 5 4
# 4 5 6
#
# 3 3
# 2 1 20
# 3 1 20
# 2 3 100
#
# Sum of weights of the optimal paths:
# 14, 40
import sys
for n_m in sys.stdin:
n, m = map(int, n_m.split())
ds = DisjointSet(m)
edges = [None] * m # Create list of size <m>
# Read <m> edges from input
for i in range(m):
u, v, weight = map(int, input().split())
u -= 1 # Convert from 1-indexed to 0-indexed
v -= 1 # Convert from 1-indexed to 0-indexed
edges[i] = Edge(u, v, weight)
# After finish input and graph creation, use Kruskal algorithm for MST:
print("MST weights sum:", kruskal(n, edges, ds))
9.11 path between two vertices in digraph¶
from collections import defaultdict
class Graph:
def __init__(self,v):
self.v = v
self.graph = defaultdict(list)
self.has_path = False
def add_edge(self,u,v):
self.graph[u].append(v)
def dfs(self,x,y):
visited = [False] * self.v
self.dfsutil(visited,x,y,)
def dfsutil(self,visited,x,y):
visited[x] = True
for i in self.graph[x]:
if y in self.graph[x]:
self.has_path = True
return
if(not(visited[i])):
self.dfsutil(visited,x,i)
def is_reachable(self,x,y):
self.has_path = False
self.dfs(x,y)
return self.has_path
# Create a graph given in the above diagram
g = Graph(4)
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(2, 3)
g.add_edge(3, 3)
u =1; v = 3
if g.is_reachable(u, v):
print("There is a path from %d to %d" % (u,v))
else :
print("There is no path from %d to %d" % (u,v))
u = 3; v = 1
if g.is_reachable(u, v) :
print("There is a path from %d to %d" % (u,v))
else :
print("There is no path from %d to %d" % (u,v))
9.12 satisfiability¶
Given a formula in conjunctive normal form (2-CNF), finds a way to assign True/False values to all variables to satisfy all clauses, or reports there is no solution.
https://en.wikipedia.org/wiki/2-satisfiability
- Format:
- each clause is a pair of literals
- each literal in the form (name, is_neg) where name is an arbitrary identifier, and is_neg is true if the literal is negated
formula = [(('x', False), ('y', False)),
(('y', True), ('y', True)),
(('a', False), ('b', False)),
(('a', True), ('c', True)),
(('c', False), ('b', True))]
def dfs_transposed(v, graph, order, vis):
vis[v] = True
for u in graph[v]:
if not vis[u]:
dfs_transposed(u, graph, order, vis)
order.append(v)
def dfs(v, current_comp, vertex_scc, graph, vis):
vis[v] = True
vertex_scc[v] = current_comp
for u in graph[v]:
if not vis[u]:
dfs(u, current_comp, vertex_scc, graph, vis)
def add_edge(graph, vertex_from, vertex_to):
if vertex_from not in graph:
graph[vertex_from] = []
graph[vertex_from].append(vertex_to)
def scc(graph):
''' Computes the strongly connected components of a graph '''
order = []
vis = {vertex: False for vertex in graph}
graph_transposed = {vertex: [] for vertex in graph}
for (v, neighbours) in graph.iteritems():
for u in neighbours:
add_edge(graph_transposed, u, v)
for v in graph:
if not vis[v]:
dfs_transposed(v, graph_transposed, order, vis)
vis = {vertex: False for vertex in graph}
vertex_scc = {}
current_comp = 0
for v in reversed(order):
if not vis[v]:
# Each dfs will visit exactly one component
dfs(v, current_comp, vertex_scc, graph, vis)
current_comp += 1
return vertex_scc
def build_graph(formula):
''' Builds the implication graph from the formula '''
graph = {}
for clause in formula:
for (lit, _) in clause:
for neg in [False, True]:
graph[(lit, neg)] = []
for ((a_lit, a_neg), (b_lit, b_neg)) in formula:
add_edge(graph, (a_lit, a_neg), (b_lit, not b_neg))
add_edge(graph, (b_lit, b_neg), (a_lit, not a_neg))
return graph
def solve_sat(formula):
graph = build_graph(formula)
vertex_scc = scc(graph)
for (var, _) in graph:
if vertex_scc[(var, False)] == vertex_scc[(var, True)]:
return None # The formula is contradictory
comp_repr = {} # An arbitrary representant from each component
for vertex in graph:
if not vertex_scc[vertex] in comp_repr:
comp_repr[vertex_scc[vertex]] = vertex
comp_value = {} # True/False value for each strongly connected component
components = sorted(vertex_scc.values())
for comp in components:
if comp not in comp_value:
comp_value[comp] = False
(lit, neg) = comp_repr[comp]
comp_value[vertex_scc[(lit, not neg)]] = True
value = {var: comp_value[vertex_scc[(var, False)]] for (var, _) in graph}
return value
if __name__ == '__main__':
result = solve_sat(formula)
for (variable, assign) in result.iteritems():
print("{}:{}".format(variable, assign))
9.13 tarjan¶
Implements Tarjan's algorithm for finding strongly connected components in a graph. https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
from algorithms.graph.graph import DirectedGraph
class Tarjan(object):
def __init__(self, dict_graph):
self.graph = DirectedGraph(dict_graph)
self.index = 0
self.stack = []
# Runs Tarjan
# Set all node index to None
for v in self.graph.nodes:
v.index = None
self.sccs = []
for v in self.graph.nodes:
if v.index == None:
self.strongconnect(v, self.sccs)
def strongconnect(self, v, sccs):
# Set the depth index for v to the smallest unused index
v.index = self.index
v.lowlink = self.index
self.index += 1
self.stack.append(v)
v.on_stack = True
# Consider successors of v
for w in self.graph.adjmt[v]:
if w.index == None:
# Successor w has not yet been visited; recurse on it
self.strongconnect(w, sccs)
v.lowlink = min(v.lowlink, w.lowlink)
elif w.on_stack:
# Successor w is in stack S and hence in the current SCC
# If w is not on stack, then (v, w) is a cross-edge in the DFS tree and must be ignored
# Note: The next line may look odd - but is correct.
# It says w.index not w.lowlink; that is deliberate and from the original paper
v.lowlink = min(v.lowlink, w.index)
# If v is a root node, pop the stack and generate an SCC
if v.lowlink == v.index:
# start a new strongly connected component
scc = []
while True:
w = self.stack.pop()
w.on_stack = False
scc.append(w)
if w == v:
break
scc.sort()
sccs.append(scc)
9.14 Transitive Closure DFS¶
# This class represents a directed graph using adjacency
class Graph:
def __init__(self, vertices):
# No. of vertices
self.V = vertices
# default dictionary to store graph
self.graph = {}
# To store transitive closure
self.tc = [[0 for j in range(self.V)] for i in range(self.V)]
# function to add an edge to graph
def add_edge(self, u, v):
if u in self.graph:
self.graph[u].append(v)
else:
self.graph[u] = [v]
# A recursive DFS traversal function that finds
# all reachable vertices for s
def dfs_util(self, s, v):
# Mark reachability from s to v as true.
self.tc[s][v] = 1
# Find all the vertices reachable through v
for i in self.graph[v]:
if self.tc[s][i] == 0:
self.dfs_util(s, i)
# The function to find transitive closure. It uses
# recursive dfs_util()
def transitive_closure(self):
# Call the recursive helper function to print DFS
# traversal starting from all vertices one by one
for i in range(self.V):
self.dfs_util(i, i)
print(self.tc)
g = Graph(4)
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(2, 3)
g.add_edge(3, 3)
print("Transitive closure matrix is")
g.transitive_closure()
9.15 traveral¶
graph = {'A': set(['B', 'C', 'F']),
'B': set(['A', 'D', 'E']),
'C': set(['A', 'F']),
'D': set(['B']),
'E': set(['B', 'F']),
'F': set(['A', 'C', 'E'])}
# dfs and bfs are the ultimately same except that they are visiting nodes in
# different order. To simulate this ordering we would use stack for dfs and
# queue for bfs.
#
def dfs_traverse(graph, start):
visited, stack = set(), [start]
while stack:
node = stack.pop()
if node not in visited:
visited.add(node)
for nextNode in graph[node]:
if nextNode not in visited:
stack.append(nextNode)
return visited
# print(dfs_traverse(graph, 'A'))
def bfs_traverse(graph, start):
visited, queue = set(), [start]
while queue:
node = queue.pop(0)
if node not in visited:
visited.add(node)
for nextNode in graph[node]:
if nextNode not in visited:
queue.append(nextNode)
return visited
# print(bfs_traverse(graph, 'A'))
def dfs_traverse_recursive(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
for nextNode in graph[start]:
if nextNode not in visited:
dfs_traverse_recursive(graph, nextNode, visited)
return visited
# print(dfs_traverse_recursive(graph, 'A'))
# def find_path(graph, start, end, visited=[]):
# # basecase
# visitied = visited + [start]
# if start == end:
# return visited
# if start not in graph:
# return None
# for node in graph[start]:
# if node not in visited:
# new_visited = find_path(graph, node, end, visited)
# return new_visited
# return None
# print(find_path(graph, 'A', 'F'))
chapter 10: Heap¶
10.1 binary heap¶
Binary Heap. A min heap is a complete binary tree where each node is smaller its childen. The root, therefore, is the minimum element in the tree. The min heap use array to represent the data and operation. For example a min heap:
4/
50 7
/ /
55 90 87
Heap [0, 4, 50, 7, 55, 90, 87]
Method in class: insert, remove_min For example insert(2) in a min heap:
4 4 2/ / /
50 7 --> 50 2 --> 50 4
/ / / / / /
55 90 87 2 55 90 87 7 55 90 87 7
For example remove_min() in a min heap:
4 87 7/ / /
50 7 --> 50 7 --> 50 87
/ / / /
55 90 87 55 90 55 90
abc class는 엄격한 방식을 택해서 상속받은 class가 구현이 안되면 바로 에러를 호출하도록 한다. 따라서 @abstractmethod 를 넣고 처리하면 에러 처리가 되고 부모 class의 instance도 생성이 되지 않으며 함수에 대한 오버라이딩도 제공하지 않는다.
from abc import ABCMeta, abstractmethod
class AbstractHeap(metaclass=ABCMeta):
"""Abstract Class for Binary Heap."""
def __init__(self):
pass
@abstractmethod
def perc_up(self, i):
pass
@abstractmethod
def insert(self, val):
pass
@abstractmethod
def perc_down(self,i):
pass
@abstractmethod
def min_child(self,i):
pass
@abstractmethod
def remove_min(self,i):
pass
class BinaryHeap(AbstractHeap):
def __init__(self):
self.currentSize = 0
self.heap = [(0)]
def perc_up(self, i):
while i // 2 > 0:
if self.heap[i] < self.heap[i // 2]:
# Swap value of child with value of its parent
self.heap[i], self.heap[i//2] = self.heap[i//2], self.heap[i]
i = i // 2
"""
Method insert always start by inserting the element at the bottom.
it inserts rightmost spot so as to maintain the complete tree property
Then, it fix the tree by swapping the new element with its parent,
until it finds an appropriate spot for the element. It essentially
perc_up the minimum element
Complexity: O(logN)
"""
def insert(self, val):
self.heap.append(val)
self.currentSize = self.currentSize + 1
self.perc_up(self.currentSize)
"""
Method min_child returns index of smaller 2 childs of its parent
"""
def min_child(self, i):
if 2 * i + 1 > self.currentSize: # No right child
return 2 * i
else:
# left child > right child
if self.heap[2 * i] > self.heap[2 * i +1]:
return 2 * i + 1
else:
return 2 * i
def perc_down(self, i):
while 2 * i < self.currentSize:
min_child = self.min_child(i)
if self.heap[min_child] < self.heap[i]:
# Swap min child with parent
self.heap[min_child], self.heap[i] = self.heap[i], self.heap[min_child]
i = min_child
"""
Remove Min method removes the minimum element and swap it with the last
element in the heap( the bottommost, rightmost element). Then, it
perc_down this element, swapping it with one of its children until the
min heap property is restored
Complexity: O(logN)
"""
def remove_min(self):
ret = self.heap[1] # the smallest value at beginning
self.heap[1] = self.heap[self.currentSize] # Repalce it by the last value
self.currentSize = self.currentSize - 1
self.heap.pop()
self.perc_down(1)
return ret
10.2 k closest points¶
Given a list of points, find the k closest to the origin.
Idea: Maintain a max heap of k elements. We can iterate through all points. If a point p has a smaller distance to the origin than the top element of a heap, we add point p to the heap and remove the top element. After iterating through all points, our heap contains the k closest points to the origin.
from heapq import heapify, heappushpop
def k_closest(points, k, origin=(0, 0)):
# Time: O(k+(n-k)logk)
# Space: O(k)
"""Initialize max heap with first k points.
Python does not support a max heap; thus we can use the default min heap where the keys (distance) are negated.
"""
heap = [(-distance(p, origin), p) for p in points[:k]]
heapify(heap)
"""
For every point p in points[k:],
check if p is smaller than the root of the max heap;
if it is, add p to heap and remove root. Reheapify.
"""
for p in points[k:]:
d = distance(p, origin)
heappushpop(heap, (-d, p)) # heappushpop does conditional check
"""Same as:
if d < -heap[0][0]:
heappush(heap, (-d,p))
heappop(heap)
Note: heappushpop is more efficient than separate push and pop calls.
Each heappushpop call takes O(logk) time.
"""
return [p for nd, p in heap] # return points in heap
def distance(point, origin=(0, 0)):
return (point[0] - origin[0])**2 + (point[1] - origin[1])**2
10.3 merge sorted k lists¶
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
from heapq import heappop, heapreplace, heapify
from queue import PriorityQueue
# Definition for singly-linked list.
class ListNode(object):
def __init__(self, x):
self.val = x
self.next = None
def merge_k_lists(lists):
dummy = node = ListNode(0)
h = [(n.val, n) for n in lists if n]
heapify(h)
while h:
v, n = h[0]
if n.next is None:
heappop(h) # only change heap size when necessary
else:
heapreplace(h, (n.next.val, n.next))
node.next = n
node = node.next
return dummy.next
def merge_k_lists(lists):
dummy = ListNode(None)
curr = dummy
q = PriorityQueue()
for node in lists:
if node:
q.put((node.val, node))
while not q.empty():
curr.next = q.get()[1] # These two lines seem to
curr = curr.next # be equivalent to :- curr = q.get()[1]
if curr.next:
q.put((curr.next.val, curr.next))
return dummy.next
I think my code's complexity is also O(nlogk) and not using heap or priority queue, n means the total elements and k means the size of list.
The mergeTwoLists function in my code comes from the problem Merge Two Sorted Lists whose complexity obviously is O(n), n is the sum of length of l1 and l2.
To put it simpler, assume the k is 2^x, So the progress of combination is like a full binary tree, from bottom to top. So on every level of tree, the combination complexity is n, because every level have all n numbers without repetition. The level of tree is x, ie log k. So the complexity is O(n log k).
for example, 8 ListNode, and the length of every ListNode is x1, x2, x3, x4, x5, x6, x7, x8, total is n.
on level 3: x1+x2, x3+x4, x5+x6, x7+x8 sum: n
on level 2: x1+x2+x3+x4, x5+x6+x7+x8 sum: n
on level 1: x1+x2+x3+x4+x5+x6+x7+x8 sum: n
10.4 skyline¶
A city's skyline is the outer contour of the silhouette formed by all the buildings in that city when viewed from a distance. Now suppose you are given the locations and height of all the buildings as shown on a cityscape photo (Figure A), write a program to output the skyline formed by these buildings collectively (Figure B).
The geometric information of each building is represented by a triplet of integers [Li, Ri, Hi], where Li and Ri are the x coordinates of the left and right edge of the ith building, respectively, and Hi is its height. It is guaranteed that 0 ≤ Li, Ri ≤ INT_MAX, 0 < Hi ≤ INT_MAX, and Ri - Li > 0. You may assume all buildings are perfect rectangles grounded on an absolutely flat surface at height 0.
For instance, the dimensions of all buildings in Figure A are recorded as: [ [2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8] ] .
The output is a list of "key points" (red dots in Figure B) in the format of [ [x1,y1], [x2, y2], [x3, y3], ... ] that uniquely defines a skyline. A key point is the left endpoint of a horizontal line segment. Note that the last key point, where the rightmost building ends, is merely used to mark the termination of the skyline, and always has zero height. Also, the ground in between any two adjacent buildings should be considered part of the skyline contour.
For instance, the skyline in Figure B should be represented as:[ [2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0] ].
Notes:
The number of buildings in any input list is guaranteed to be in the range [0, 10000]. The input list is already sorted in ascending order by the left x position Li. The output list must be sorted by the x position. There must be no consecutive horizontal lines of equal height in the output skyline. For instance, [...[2 3], [4 5], [7 5], [11 5], [12 7]...] is not acceptable; the three lines of height 5 should be merged into one in the final output as such: [...[2 3], [4 5], [12 7], ...]
import heapq
def get_skyline(lrh):
"""
Wortst Time Complexity: O(NlogN)
:type buildings: List[List[int]]
:rtype: List[List[int]]
"""
skyline, live = [], []
i, n = 0, len(lrh)
while i < n or live:
if not live or i < n and lrh[i][0] <= -live[0][1]:
x = lrh[i][0]
while i < n and lrh[i][0] == x:
heapq.heappush(live, (-lrh[i][2], -lrh[i][1]))
i += 1
else:
x = -live[0][1]
while live and -live[0][1] <= x:
heapq.heappop(live)
height = len(live) and -live[0][0]
if not skyline or height != skyline[-1][1]:
skyline += [x, height],
return skyline
10.5 sliding window max¶
Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
For example, Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.
Window position Max --------------- ----- [1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
Therefore, return the max sliding window as [3,3,5,5,6,7].
import collections
def max_sliding_window(nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
if not nums:
return nums
queue = collections.deque()
res = []
for num in nums:
if len(queue) < k:
queue.append(num)
else:
res.append(max(queue))
queue.popleft()
queue.append(num)
res.append(max(queue))
return res
chapter 11: Linked list¶
11.1 add two numbers¶
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) Output: 7 -> 0 -> 8
import unittest
class Node:
def __init__(self, x):
self.val = x
self.next = None
def add_two_numbers(left: Node, right: Node) -> Node:
head = Node(0)
current = head
sum = 0
while left or right:
print("adding: ", left.val, right.val)
sum //= 10
if left:
sum += left.val
left = left.next
if right:
sum += right.val
right = right.next
current.next = Node(sum % 10)
current = current.next
if sum // 10 == 1:
current.next = Node(1)
return head.next
def convert_to_list(number: int) -> Node:
"""
converts a positive integer into a (reversed) linked list.
for example: give 112
result 2 -> 1 -> 1
"""
if number >= 0:
head = Node(0)
current = head
remainder = number % 10
quotient = number // 10
while quotient != 0:
current.next = Node(remainder)
current = current.next
remainder = quotient % 10
quotient //= 10
current.next = Node(remainder)
return head.next
else:
print("number must be positive!")
def convert_to_str(l: Node) -> str:
"""
converts the non-negative number list into a string.
"""
result = ""
while l:
result += str(l.val)
l = l.next
return result
class TestSuite(unittest.TestCase):
"""
testsuite for the linked list structure and
the adding function, above.
"""
def test_convert_to_str(self):
number1 = Node(2)
number1.next = Node(4)
number1.next.next = Node(3)
self.assertEqual("243", convert_to_str(number1))
def test_add_two_numbers(self):
# 1. test case
number1 = Node(2)
number1.next = Node(4)
number1.next.next = Node(3)
number2 = Node(5)
number2.next = Node(6)
number2.next.next = Node(4)
result = convert_to_str(add_two_numbers(number1, number2))
self.assertEqual("708", result)
# 2. test case
number3 = Node(1)
number3.next = Node(1)
number3.next.next = Node(9)
number4 = Node(1)
number4.next = Node(0)
number4.next.next = Node(1)
result = convert_to_str(add_two_numbers(number3, number4))
self.assertEqual("2101", result)
# 3. test case
number5 = Node(1)
number6 = Node(0)
result = convert_to_str(add_two_numbers(number5, number6))
self.assertEqual("1", result)
# 4. test case
number7 = Node(9)
number7.next = Node(1)
number7.next.next = Node(1)
number8 = Node(1)
number8.next = Node(0)
number8.next.next = Node(1)
result = convert_to_str(add_two_numbers(number7, number8))
self.assertEqual("022", result)
def test_convert_to_list(self):
result = convert_to_str(convert_to_list(112))
self.assertEqual("211", result)
if __name__ == "__main__":
unittest.main()
11.2 copy random pointer¶
A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
from collections import defaultdict
class RandomListNode(object):
def __init__(self, label):
self.label = label
self.next = None
self.random = None
def copy_random_pointer_v1(head):
"""
:type head: RandomListNode
:rtype: RandomListNode
"""
dic = dict()
m = n = head
while m:
dic[m] = RandomListNode(m.label)
m = m.next
while n:
dic[n].next = dic.get(n.next)
dic[n].random = dic.get(n.random)
n = n.next
return dic.get(head)
# O(n)
def copy_random_pointer_v2(head):
"""
:type head: RandomListNode
:rtype: RandomListNode
"""
copy = defaultdict(lambda: RandomListNode(0))
copy[None] = None
node = head
while node:
copy[node].label = node.label
copy[node].next = copy[node.next]
copy[node].random = copy[node.random]
node = node.next
return copy[head]
11.3 delete node¶
Write a function to delete a node (except the tail) in a singly linked list, given only access to that node.
Supposed the linked list is 1 -> 2 -> 3 -> 4 and you are given the third node with value 3, the linked list should become 1 -> 2 -> 4 after calling your function.
import unittest
class Node:
def __init__(self, x):
self.val = x
self.next = None
def delete_node(node):
if node is None or node.next is None:
raise ValueError
node.val = node.next.val
node.next = node.next.next
class TestSuite(unittest.TestCase):
def test_delete_node(self):
# make linkedlist 1 -> 2 -> 3 -> 4
head = Node(1)
curr = head
for i in range(2, 6):
curr.next = Node(i)
curr = curr.next
# node3 = 3
node3 = head.next.next
# after delete_node => 1 -> 2 -> 4
delete_node(node3)
curr = head
self.assertEqual(1, curr.val)
curr = curr.next
self.assertEqual(2, curr.val)
curr = curr.next
self.assertEqual(4, curr.val)
curr = curr.next
self.assertEqual(5, curr.val)
tail = curr
self.assertIsNone(tail.next)
self.assertRaises(ValueError, delete_node, tail)
self.assertRaises(ValueError, delete_node, tail.next)
if __name__ == '__main__':
unittest.main()
11.4 first cyclic node¶
Given a linked list, find the first node of a cycle in it. 1 -> 2 -> 3 -> 4 -> 5 -> 1 => 1 A -> B -> C -> D -> E -> C => C
- Note: The solution is a direct implementation
- Floyd's cycle-finding algorithm (Floyd's Tortoise and Hare).
import unittest
class Node:
def __init__(self, x):
self.val = x
self.next = None
def first_cyclic_node(head):
"""
:type head: Node
:rtype: Node
"""
runner = walker = head
while runner and runner.next:
runner = runner.next.next
walker = walker.next
if runner is walker:
break
if runner is None or runner.next is None:
return None
walker = head
while runner is not walker:
runner, walker = runner.next, walker.next
return runner
class TestSuite(unittest.TestCase):
def test_first_cyclic_node(self):
# create linked list => A -> B -> C -> D -> E -> C
head = Node('A')
head.next = Node('B')
curr = head.next
cyclic_node = Node('C')
curr.next = cyclic_node
curr = curr.next
curr.next = Node('D')
curr = curr.next
curr.next = Node('E')
curr = curr.next
curr.next = cyclic_node
self.assertEqual('C', first_cyclic_node(head).val)
if __name__ == '__main__':
unittest.main()
11.5 intersection¶
This function takes two lists and returns the node they have in common, if any. In this example: 1 -> 3 -> 5
- 7 -> 9 -> 11
/
2 -> 4 -> 6 ...we would return 7. Note that the node itself is the unique identifier, not the value of the node.
import unittest
class Node(object):
def __init__(self, val=None):
self.val = val
self.next = None
def intersection(h1, h2):
count = 0
flag = None
h1_orig = h1
h2_orig = h2
while h1 or h2:
count += 1
if not flag and (h1.next is None or h2.next is None):
# We hit the end of one of the lists, set a flag for this
flag = (count, h1.next, h2.next)
if h1:
h1 = h1.next
if h2:
h2 = h2.next
long_len = count # Mark the length of the longer of the two lists
short_len = flag[0]
if flag[1] is None:
shorter = h1_orig
longer = h2_orig
elif flag[2] is None:
shorter = h2_orig
longer = h1_orig
while longer and shorter:
while long_len > short_len:
# force the longer of the two lists to "catch up"
longer = longer.next
long_len -= 1
if longer == shorter:
# The nodes match, return the node
return longer
else:
longer = longer.next
shorter = shorter.next
return None
class TestSuite(unittest.TestCase):
def test_intersection(self):
# create linked list as:
# 1 -> 3 -> 5
# \
# 7 -> 9 -> 11
# /
# 2 -> 4 -> 6
a1 = Node(1)
b1 = Node(3)
c1 = Node(5)
d = Node(7)
a2 = Node(2)
b2 = Node(4)
c2 = Node(6)
e = Node(9)
f = Node(11)
a1.next = b1
b1.next = c1
c1.next = d
a2.next = b2
b2.next = c2
c2.next = d
d.next = e
e.next = f
self.assertEqual(7, intersection(a1, a2).val)
if __name__ == '__main__':
unittest.main()
11.6 is cyclic¶
Given a linked list, determine if it has a cycle in it.
Follow up: Can you solve it without using extra space?
class Node:
def __init__(self, x):
self.val = x
self.next = None
def is_cyclic(head):
"""
:type head: Node
:rtype: bool
"""
if not head:
return False
runner = head
walker = head
while runner.next and runner.next.next:
runner = runner.next.next
walker = walker.next
if runner == walker:
return True
return False
11.7 is paliindrome¶
def is_palindrome(head):
if not head:
return True
# split the list to two parts
fast, slow = head.next, head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
second = slow.next
slow.next = None # Don't forget here! But forget still works!
# reverse the second part
node = None
while second:
nxt = second.next
second.next = node
node = second
second = nxt
# compare two parts
# second part has the same or one less node
while node:
if node.val != head.val:
return False
node = node.next
head = head.next
return True
def is_palindrome_stack(head):
if not head or not head.next:
return True
# 1. Get the midpoint (slow)
slow = fast = cur = head
while fast and fast.next:
fast, slow = fast.next.next, slow.next
# 2. Push the second half into the stack
stack = [slow.val]
while slow.next:
slow = slow.next
stack.append(slow.val)
# 3. Comparison
while stack:
if stack.pop() != cur.val:
return False
cur = cur.next
return True
def is_palindrome_dict(head):
"""
This function builds up a dictionary where the keys are the values of the list,
and the values are the positions at which these values occur in the list.
We then iterate over the dict and if there is more than one key with an odd
number of occurrences, bail out and return False.
Otherwise, we want to ensure that the positions of occurrence sum to the
value of the length of the list - 1, working from the outside of the list inward.
For example:
Input: 1 -> 1 -> 2 -> 3 -> 2 -> 1 -> 1
d = {1: [0,1,5,6], 2: [2,4], 3: [3]}
'3' is the middle outlier, 2+4=6, 0+6=6 and 5+1=6 so we have a palindrome.
"""
if not head or not head.next:
return True
d = {}
pos = 0
while head:
if head.val in d.keys():
d[head.val].append(pos)
else:
d[head.val] = [pos]
head = head.next
pos += 1
checksum = pos - 1
middle = 0
for v in d.values():
if len(v) % 2 != 0:
middle += 1
else:
step = 0
for i in range(0, len(v)):
if v[i] + v[len(v) - 1 - step] != checksum:
return False
step += 1
if middle > 1:
return False
return True
11.8 is sorted¶
Given a linked list, is_sort function returns true if the list is in sorted (increasing) order and return false otherwise. An empty list is considered to be sorted.
For example: Null :List is sorted 1 2 3 4 :List is sorted 1 2 -1 3 :List is not sorted
def is_sorted(head):
if not head:
return True
current = head
while current.next:
if current.val > current.next.val:
return False
current = current.next
return True
11.9 kth to last¶
class Node():
def __init__(self, val=None):
self.val = val
self.next = None
def kth_to_last_eval(head, k):
"""
This is a suboptimal, hacky method using eval(), which is not
safe for user input. We guard against danger by ensuring k in an int
"""
if not isinstance(k, int) or not head.val:
return False
nexts = '.'.join(['next' for n in range(1, k+1)])
seeker = str('.'.join(['head', nexts]))
while head:
if eval(seeker) is None:
return head
else:
head = head.next
return False
def kth_to_last_dict(head, k):
"""
This is a brute force method where we keep a dict the size of the list
Then we check it for the value we need. If the key is not in the dict,
our and statement will short circuit and return False
"""
if not (head and k > -1):
return False
d = dict()
count = 0
while head:
d[count] = head
head = head.next
count += 1
return len(d)-k in d and d[len(d)-k]
def kth_to_last(head, k):
"""
This is an optimal method using iteration.
We move p1 k steps ahead into the list.
Then we move p1 and p2 together until p1 hits the end.
"""
if not (head or k > -1):
return False
p1 = head
p2 = head
for i in range(1, k+1):
if p1 is None:
# Went too far, k is not valid
raise IndexError
p1 = p1.next
while p1:
p1 = p1.next
p2 = p2.next
return p2
def print_linked_list(head):
string = ""
while head.next:
string += head.val + " -> "
head = head.next
string += head.val
print(string)
def test():
# def make_test_li
# A A B C D C F G
a1 = Node("A")
a2 = Node("A")
b = Node("B")
c1 = Node("C")
d = Node("D")
c2 = Node("C")
f = Node("F")
g = Node("G")
a1.next = a2
a2.next = b
b.next = c1
c1.next = d
d.next = c2
c2.next = f
f.next = g
print_linked_list(a1)
# test kth_to_last_eval
kth = kth_to_last_eval(a1, 4)
try:
assert kth.val == "D"
except AssertionError as e:
e.args += ("Expecting D, got %s" % kth.val,)
raise
# test kth_to_last_dict
kth = kth_to_last_dict(a1, 4)
try:
assert kth.val == "D"
except AssertionError as e:
e.args += ("Expecting D, got %s" % kth.val,)
raise
# test kth_to_last
kth = kth_to_last(a1, 4)
try:
assert kth.val == "D"
except AssertionError as e:
e.args += ("Expecting D, got %s" % kth.val,)
raise
print("all passed.")
if __name__ == '__main__':
test()
11.10 linked list¶
# Pros # Linked Lists have constant-time insertions and deletions in any position, # in comparison, arrays require O(n) time to do the same thing. # Linked lists can continue to expand without having to specify # their size ahead of time (remember our lectures on Array sizing # form the Array Sequence section of the course!)
# Cons # To access an element in a linked list, you need to take O(k) time # to go from the head of the list to the kth element. # In contrast, arrays have constant time operations to access # elements in an array.
class DoublyLinkedListNode(object):
def __init__(self, value):
self.value = value
self.next = None
self.prev = None
class SinglyLinkedListNode(object):
def __init__(self, value):
self.value = value
self.next = None
11.11 merge two list¶
Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.
For example: Input: 1->2->4, 1->3->4 Output: 1->1->2->3->4->4
class Node:
def __init__(self, x):
self.val = x
self.next = None
def merge_two_list(l1, l2):
ret = cur = Node(0)
while l1 and l2:
if l1.val < l2.val:
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 or l2
return ret.next
# recursively
def merge_two_list_recur(l1, l2):
if not l1 or not l2:
return l1 or l2
if l1.val < l2.val:
l1.next = merge_two_list_recur(l1.next, l2)
return l1
else:
l2.next = merge_two_list_recur(l1, l2.next)
return l2
11.12 partition¶
Write code to partition a linked list around a value x, such that all nodes less than x come before all nodes greater than or equal to x. If x is contained within the list, the values of x only need to be after the elements less than x. The partition element x can appear anywhere in the "right partition"; it does not need to appear between the left and right partitions.
3 -> 5 -> 8 -> 5 -> 10 -> 2 -> 1 [partition=5] 3 -> 1 -> 2 -> 10 -> 5 -> 5 -> 8
We assume the values of all linked list nodes are int and that x in an int.
class Node():
def __init__(self, val=None):
self.val = int(val)
self.next = None
def print_linked_list(head):
string = ""
while head.next:
string += str(head.val) + " -> "
head = head.next
string += str(head.val)
print(string)
def partition(head, x):
left = None
right = None
prev = None
current = head
while current:
if int(current.val) >= x:
if not right:
right = current
else:
if not left:
left = current
else:
prev.next = current.next
left.next = current
left = current
left.next = right
if prev and prev.next is None:
break
# cache previous value in case it needs to be pointed elsewhere
prev = current
current = current.next
def test():
a = Node("3")
b = Node("5")
c = Node("8")
d = Node("5")
e = Node("10")
f = Node("2")
g = Node("1")
a.next = b
b.next = c
c.next = d
d.next = e
e.next = f
f.next = g
print_linked_list(a)
partition(a, 5)
print_linked_list(a)
if __name__ == '__main__':
test()
11.13 remove duplicates¶
class Node():
def __init__(self, val = None):
self.val = val
self.next = None
def remove_dups(head):
"""
Time Complexity: O(N)
Space Complexity: O(N)
"""
hashset = set()
prev = Node()
while head:
if head.val in hashset:
prev.next = head.next
else:
hashset.add(head.val)
prev = head
head = head.next
def remove_dups_wothout_set(head):
"""
Time Complexity: O(N^2)
Space Complexity: O(1)
"""
current = head
while current:
runner = current
while runner.next:
if runner.next.val == current.val:
runner.next = runner.next.next
else:
runner = runner.next
current = current.next
def print_linked_list(head):
string = ""
while head.next:
string += head.val + " -> "
head = head.next
string += head.val
print(string)
# A A B C D C F G
a1 = Node("A")
a2 = Node("A")
b = Node("B")
c1 = Node("C")
d = Node("D")
c2 = Node("C")
f = Node("F")
g = Node("G")
a1.next = a2
a2.next = b
b.next = c1
c1.next = d
d.next = c2
c2.next = f
f.next = g
remove_dups(a1)
print_linked_list(a1)
remove_dups_wothout_set(a1)
print_linked_list(a1)
11.14 remove range¶
Given a linked list, remove_range function accepts a starting and ending index as parameters and removes the elements at those indexes (inclusive) from the list
For example: List: [8, 13, 17, 4, 9, 12, 98, 41, 7, 23, 0, 92] remove_range(list, 3, 8); List becomes: [8, 13, 17, 23, 0, 92]
legal range of the list (0 < start index < end index < size of list).
def remove_range(head, start, end):
assert(start <= end)
# Case: remove node at head
if start == 0:
for i in range(0, end+1):
if head != None:
head = head.next
else:
current = head
# Move pointer to start position
for i in range(0,start-1):
current = current.next
# Remove data until the end
for i in range(0, end-start + 1):
if current != None and current.next != None:
current.next = current.next.next
return head
11.15 reverse¶
Reverse a singly linked list. For example:
1 --> 2 --> 3 --> 4 After reverse: 4 --> 3 --> 2 --> 1
#
# Iterative solution
# T(n)- O(n)
#
def reverse_list(head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return head
prev = None
while head:
current = head
head = head.next
current.next = prev
prev = current
return prev
#
# Recursive solution
# T(n)- O(n)
#
def reverse_list_recursive(head):
"""
:type head: ListNode
:rtype: ListNode
"""
if head is None or head.next is None:
return head
p = head.next
head.next = None
revrest = reverse_list_recursive(p)
p.next = head
return revrest
11.16 rotate list¶
Given a list, rotate the list to the right by k places, where k is non-negative.
For example: Given 1->2->3->4->5->NULL and k = 2, return 4->5->1->2->3->NULL.
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
def rotate_right(head, k):
"""
:type head: ListNode
:type k: int
:rtype: ListNode
"""
if not head or not head.next:
return head
current = head
length = 1
# count length of the list
while current.next:
current = current.next
length += 1
# make it circular
current.next = head
k = k % length
# rotate until length-k
for i in range(length-k):
current = current.next
head = current.next
current.next = None
return head
11.17 swap in pairs¶
Given a linked list, swap every two adjacent nodes and return its head.
For example, Given 1->2->3->4, you should return the list as 2->1->4->3.
Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed.
class Node(object):
def __init__(self, x):
self.val = x
self.next = None
def swap_pairs(head):
if not head:
return head
start = Node(0)
start.next = head
current = start
while current.next and current.next.next:
first = current.next
second = current.next.next
first.next = second.next
current.next = second
current.next.next = first
current = current.next.next
return start.next
chapter 12: Map¶
12.1 hashtable¶
HashMap Data Type HashMap() Create a new, empty map. It returns an empty map collection. put(key, val) Add a new key-value pair to the map. If the key is already in the map then replace
the old value with the new value.
get(key) Given a key, return the value stored in the map or None otherwise. del_(key) or del map[key] Delete the key-value pair from the map using a statement of the form del map[key]. len() Return the number of key-value pairs stored in the map. in Return True for a statement of the form key in map, if the given key is in the map, False otherwise.
_empty = object()
_deleted = object()
def __init__(self, size=11):
self.size = size
self._len = 0
self._keys = [self._empty] * size # keys
self._values = [self._empty] * size # values
def put(self, key, value):
initial_hash = hash_ = self.hash(key)
while True:
if self._keys[hash_] is self._empty or self._keys[hash_] is self._deleted:
# can assign to hash_ index
self._keys[hash_] = key
self._values[hash_] = value
self._len += 1
return
elif self._keys[hash_] == key:
# key already exists here, assign over
self._keys[hash_] = key
self._values[hash_] = value
return
hash_ = self._rehash(hash_)
if initial_hash == hash_:
# table is full
raise ValueError("Table is full")
def get(self, key):
initial_hash = hash_ = self.hash(key)
while True:
if self._keys[hash_] is self._empty:
# That key was never assigned
return None
elif self._keys[hash_] == key:
# key found
return self._values[hash_]
hash_ = self._rehash(hash_)
if initial_hash == hash_:
# table is full and wrapped around
return None
def del_(self, key):
initial_hash = hash_ = self.hash(key)
while True:
if self._keys[hash_] is self._empty:
# That key was never assigned
return None
elif self._keys[hash_] == key:
# key found, assign with deleted sentinel
self._keys[hash_] = self._deleted
self._values[hash_] = self._deleted
self._len -= 1
return
hash_ = self._rehash(hash_)
if initial_hash == hash_:
# table is full and wrapped around
return None
def hash(self, key):
return key % self.size
def _rehash(self, old_hash):
"""
linear probing
"""
return (old_hash + 1) % self.size
def __getitem__(self, key):
return self.get(key)
def __delitem__(self, key):
return self.del_(key)
def __setitem__(self, key, value):
self.put(key, value)
def __len__(self):
return self._len
class ResizableHashTable(HashTable):
MIN_SIZE = 8
def __init__(self):
super().__init__(self.MIN_SIZE)
def put(self, key, value):
rv = super().put(key, value)
# increase size of dict * 2 if filled >= 2/3 size (like python dict)
if len(self) >= (self.size * 2) / 3:
self.__resize()
def __resize(self):
keys, values = self._keys, self._values
self.size *= 2 # this will be the new size
self._len = 0
self._keys = [self._empty] * self.size
self._values = [self._empty] * self.size
for key, value in zip(keys, values):
if key is not self._empty and key is not self._deleted:
self.put(key, value)
12.2 is anagram¶
Given two strings s and t , write a function to determine if t is an anagram of s.
Example 1: Input: s = "anagram", t = "nagaram" Output: true
Example 2: Input: s = "rat", t = "car" Output: false
Note: You may assume the string contains only lowercase alphabets.
Reference: https://leetcode.com/problems/valid-anagram/description/
def is_anagram(s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
maps = {}
mapt = {}
for i in s:
maps[i] = maps.get(i, 0) + 1
for i in t:
mapt[i] = mapt.get(i, 0) + 1
return maps == mapt
12.3 is isomorphic¶
Given two strings s and t, determine if they are isomorphic. Two strings are isomorphic if the characters in s can be replaced to get t. All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.
Example 1: Input: s = "egg", t = "add" Output: true
Example 2: Input: s = "foo", t = "bar" Output: false
Example 3: Input: s = "paper", t = "title" Output: true Reference: https://leetcode.com/problems/isomorphic-strings/description/
def is_isomorphic(s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
if len(s) != len(t):
return False
dict = {}
set_value = set()
for i in range(len(s)):
if s[i] not in dict:
if t[i] in set_value:
return False
dict[s[i]] = t[i]
set_value.add(t[i])
else:
if dict[s[i]] != t[i]:
return False
return True
12.4 logest common subsequence¶
Given string a and b, with b containing all distinct characters, find the longest common sub sequence's length.
Expected complexity O(n logn).
def max_common_sub_string(s1, s2):
# Assuming s2 has all unique chars
s2dic = {s2[i]: i for i in range(len(s2))}
maxr = 0
subs = ''
i = 0
while i < len(s1):
if s1[i] in s2dic:
j = s2dic[s1[i]]
k = i
while j < len(s2) and k < len(s1) and s1[k] == s2[j]:
k += 1
j += 1
if k - i > maxr:
maxr = k-i
subs = s1[i:k]
i = k
else:
i += 1
return subs
12.5 randomized set¶
Design a data structure that supports all following operations in average O(1) time.
insert(val): Inserts an item val to the set if not already present. remove(val): Removes an item val from the set if present. getRandom: Returns a random element from current set of elements. Each element must have the same probability of being returned.
import random
class RandomizedSet:
def __init__(self):
self.nums = []
self.idxs = {}
def insert(self, val):
if val not in self.idxs:
self.nums.append(val)
self.idxs[val] = len(self.nums)-1
return True
return False
def remove(self, val):
if val in self.idxs:
idx, last = self.idxs[val], self.nums[-1]
self.nums[idx], self.idxs[last] = last, idx
self.nums.pop()
self.idxs.pop(val, 0)
return True
return False
def get_random(self):
idx = random.randint(0, len(self.nums)-1)
return self.nums[idx]
if __name__ == "__main__":
rs = RandomizedSet()
print("insert 1: ", rs.insert(1))
print("insert 2: ", rs.insert(2))
print("insert 3: ", rs.insert(3))
print("insert 4: ", rs.insert(4))
print("remove 3: ", rs.remove(3))
print("remove 3: ", rs.remove(3))
print("remove 1: ", rs.remove(1))
print("random: ", rs.get_random())
print("random: ", rs.get_random())
print("random: ", rs.get_random())
print("random: ", rs.get_random())
12.6 separate chaining hashtable¶
import unittest
class Node(object):
def __init__(self, key=None, value=None, next=None):
self.key = key
self.value = value
self.next = next
class SeparateChainingHashTable(object):
"""
HashTable Data Type:
By having each bucket contain a linked list of elements that are hashed to that bucket.
Usage:
>>> table = SeparateChainingHashTable() # Create a new, empty map.
>>> table.put('hello', 'world') # Add a new key-value pair.
>>> len(table) # Return the number of key-value pairs stored in the map.
1
>>> table.get('hello') # Get value by key.
'world'
>>> del table['hello'] # Equivalent to `table.del_('hello')`, deleting key-value pair.
>>> table.get('hello') is None # Return `None` if a key doesn't exist.
True
"""
_empty = None
def __init__(self, size=11):
self.size = size
self._len = 0
self._table = [self._empty] * size
def put(self, key, value):
hash_ = self.hash(key)
node_ = self._table[hash_]
if node_ is self._empty:
self._table[hash_] = Node(key, value)
else:
while node_.next is not None:
if node_.key == key:
node_.value = value
return
node_ = node_.next
node_.next = Node(key, value)
self._len += 1
def get(self, key):
hash_ = self.hash(key)
node_ = self._table[hash_]
while node_ is not self._empty:
if node_.key == key:
return node_.value
node_ = node_.next
return None
def del_(self, key):
hash_ = self.hash(key)
node_ = self._table[hash_]
pre_node = None
while node_ is not None:
if node_.key == key:
if pre_node is None:
self._table[hash_] = node_.next
else:
pre_node.next = node_.next
self._len -= 1
pre_node = node_
node_ = node_.next
def hash(self, key):
return hash(key) % self.size
def __len__(self):
return self._len
def __getitem__(self, key):
return self.get(key)
def __delitem__(self, key):
return self.del_(key)
def __setitem__(self, key, value):
self.put(key, value)
12.7 valid sudoku¶
Determine if a Sudoku is valid, according to: Sudoku Puzzles - The Rules.
The Sudoku board could be partially filled, where empty cells are filled with the character '.'.
def is_valid_sudoku(self, board):
seen = []
for i, row in enumerate(board):
for j, c in enumerate(row):
if c != '.':
seen += [(c,j),(i,c),(i/3,j/3,c)]
return len(seen) == len(set(seen))
12.8 word pattern¶
Given a pattern and a string str, find if str follows the same pattern. Here follow means a full match, such that there is a bijection between a letter in pattern and a non-empty word in str.
Example 1: Input: pattern = "abba", str = "dog cat cat dog" Output: true
Example 2: Input:pattern = "abba", str = "dog cat cat fish" Output: false
Example 3: Input: pattern = "aaaa", str = "dog cat cat dog" Output: false
Example 4: Input: pattern = "abba", str = "dog dog dog dog" Output: false Notes: You may assume pattern contains only lowercase letters, and str contains lowercase letters separated by a single space. Reference: https://leetcode.com/problems/word-pattern/description/
def word_pattern(pattern, str):
dict = {}
set_value = set()
list_str = str.split()
if len(list_str) != len(pattern):
return False
for i in range(len(pattern)):
if pattern[i] not in dict:
if list_str[i] in set_value:
return False
dict[pattern[i]] = list_str[i]
set_value.add(list_str[i])
else:
if dict[pattern[i]] != list_str[i]:
return False
return True
chapter 13: Maths¶
13.1 base conversion¶
Integer base conversion algorithm
int2base(5, 2) return '101'. base2int('F', 16) return 15.
import string
def int_to_base(n, base):
"""
:type n: int
:type base: int
:rtype: str
"""
is_negative = False
if n == 0:
return '0'
elif n < 0:
is_negative = True
n *= -1
digit = string.digits + string.ascii_uppercase
res = ''
while n > 0:
res += digit[n % base]
n //= base
if is_negative:
return '-' + res[::-1]
else:
return res[::-1]
def base_to_int(s, base):
"""
Note : You can use int() built-in function instread of this.
:type s: str
:type base: int
:rtype: int
"""
digit = {}
for i,c in enumerate(string.digits + string.ascii_uppercase):
digit[c] = i
multiplier = 1
res = 0
for c in s[::-1]:
res += digit[c] * multiplier
multiplier *= base
return res
13.2 combination¶
def combination(n, r):
"""This function calculates nCr."""
if n == r or r == 0:
return 1
else:
return combination(n-1, r-1) + combination(n-1, r)
def combination_memo(n, r):
"""This function calculates nCr using memoization method."""
memo = {}
def recur(n, r):
if n == r or r == 0:
return 1
if (n, r) not in memo:
memo[(n, r)] = recur(n - 1, r - 1) + recur(n - 1, r)
return memo[(n, r)]
return recur(n, r)
13.3 decimal to binary ip¶
Given an ip address in dotted-decimal representation, determine the binary representation. For example, decimal_to_binary(255.0.0.5) returns 11111111.00000000.00000000.00000101 accepts string returns string
def decimal_to_binary_util(val):
bits = [128, 64, 32, 16, 8, 4, 2, 1]
val = int(val)
binary_rep = ''
for bit in bits:
if val >= bit:
binary_rep += str(1)
val -= bit
else:
binary_rep += str(0)
return binary_rep
def decimal_to_binary_ip(ip):
values = ip.split('.')
binary_list = []
for val in values:
binary_list.append(decimal_to_binary_util(val))
return '.'.join(binary_list)
13.4 euler totient¶
Euler's totient function, also known as phi-function ϕ(n), counts the number of integers between 1 and n inclusive, which are coprime to n. (Two numbers are coprime if their greatest common divisor (GCD) equals 1).
def euler_totient(n):
"""Euler's totient function or Phi function.
Time Complexity: O(sqrt(n))."""
result = n;
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
while n % i == 0:
n //= i
result -= result // i
if n > 1:
result -= result // n;
return result;
13.5 extended gcd¶
Extended GCD algorithm. Return s, t, g such that a * s + b * t = GCD(a, b) and s and t are co-prime.
old_s, s = 1, 0
old_t, t = 0, 1
old_r, r = a, b
while r != 0:
quotient = old_r / r
old_r, r = r, old_r - quotient * r
old_s, s = s, old_s - quotient * s
old_t, t = t, old_t - quotient * t
return old_s, old_t, old_r
13.6 factorial¶
def factorial(n, mod=None):
"""Calculates factorial iteratively.
If mod is not None, then return (n! % mod)
Time Complexity - O(n)"""
if not (isinstance(n, int) and n >= 0):
raise ValueError("'n' must be a non-negative integer.")
if mod is not None and not (isinstance(mod, int) and mod > 0):
raise ValueError("'mod' must be a positive integer")
result = 1
if n == 0:
return 1
for i in range(2, n+1):
result *= i
if mod:
result %= mod
return result
def factorial_recur(n, mod=None):
"""Calculates factorial recursively.
If mod is not None, then return (n! % mod)
Time Complexity - O(n)"""
if not (isinstance(n, int) and n >= 0):
raise ValueError("'n' must be a non-negative integer.")
if mod is not None and not (isinstance(mod, int) and mod > 0):
raise ValueError("'mod' must be a positive integer")
if n == 0:
return 1
result = n * factorial(n - 1, mod)
if mod:
result %= mod
return result
13.7 gcd¶
def gcd(a, b):
"""Computes the greatest common divisor of integers a and b using
Euclid's Algorithm.
"""
while b != 0:
a, b = b, a % b
return a
def lcm(a, b):
"""Computes the lowest common multiple of integers a and b."""
return a * b / gcd(a, b)
13.8 generate stobogrammtic¶
A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).
Find all strobogrammatic numbers that are of length = n.
For example, Given n = 2, return ["11","69","88","96"].
def gen_strobogrammatic(n):
"""
:type n: int
:rtype: List[str]
"""
return helper(n, n)
def helper(n, length):
if n == 0:
return [""]
if n == 1:
return ["1", "0", "8"]
middles = helper(n-2, length)
result = []
for middle in middles:
if n != length:
result.append("0" + middle + "0")
result.append("8" + middle + "8")
result.append("1" + middle + "1")
result.append("9" + middle + "6")
result.append("6" + middle + "9")
return result
def strobogrammatic_in_range(low, high):
"""
:type low: str
:type high: str
:rtype: int
"""
res = []
count = 0
low_len = len(low)
high_len = len(high)
for i in range(low_len, high_len + 1):
res.extend(helper2(i, i))
for perm in res:
if len(perm) == low_len and int(perm) < int(low):
continue
elif len(perm) == high_len and int(perm) > int(high):
continue
else:
count += 1
return count
def helper2(n, length):
if n == 0:
return [""]
if n == 1:
return ["0", "8", "1"]
mids = helper(n-2, length)
res = []
for mid in mids:
if n != length:
res.append("0"+mid+"0")
res.append("1"+mid+"1")
res.append("6"+mid+"9")
res.append("9"+mid+"6")
res.append("8"+mid+"8")
return res
13.9 hailstone¶
def hailstone(n):
"""Return the 'hailstone sequence' from n to 1
n: The starting point of the hailstone sequence
"""
sequence = [n]
while n > 1:
if n%2 != 0:
n = 3*n + 1
else:
n = int(n/2)
sequence.append(n)
return sequence
13.10 is strobogrammatic¶
A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).
Write a function to determine if a number is strobogrammatic. The number is represented as a string.
For example, the numbers "69", "88", and "818" are all strobogrammatic.
def is_strobogrammatic(num):
"""
:type num: str
:rtype: bool
"""
comb = "00 11 88 69 96"
i = 0
j = len(num) - 1
while i <= j:
x = comb.find(num[i]+num[j])
if x == -1:
return False
i += 1
j -= 1
return True
def is_strobogrammatic2(num: str):
"""Another implementation."""
return num == num[::-1].replace('6', '#').replace('9', '6').replace('#', '9')
13.11 moduler exponetial¶
def modular_exponential(base, exponent, mod):
"""Computes (base ^ exponent) % mod.
Time complexity - O(log n)
Use similar to Python in-built function pow."""
if exponent < 0:
raise ValueError("Exponent must be positive.")
base %= mod
result = 1
while exponent > 0:
# If the last bit is 1, add 2^k.
if exponent & 1:
result = (result * base) % mod
exponent = exponent >> 1
# Utilize modular multiplication properties to combine the computed mod C values.
base = (base * base) % mod
return result
13.12 next bigger¶
I just bombed an interview and made pretty much zero progress on my interview question.
Given a number, find the next higher number which has the exact same set of digits as the original number. For example: given 38276 return 38627.
given 99999 return -1. (no such number exists)
Condensed mathematical description:
Find largest index i such that array[i − 1] < array[i]. (If no such i exists, then this is already the last permutation.)
Find largest index j such that j ≥ i and array[j] > array[i − 1].
Swap array[j] and array[i − 1].
Reverse the suffix starting at array[i].
import unittest
def next_bigger(num):
digits = [int(i) for i in str(num)]
idx = len(digits) - 1
while idx >= 1 and digits[idx-1] >= digits[idx]:
idx -= 1
if idx == 0:
return -1 # no such number exists
pivot = digits[idx-1]
swap_idx = len(digits) - 1
while pivot >= digits[swap_idx]:
swap_idx -= 1
digits[swap_idx], digits[idx-1] = digits[idx-1], digits[swap_idx]
digits[idx:] = digits[:idx-1:-1] # prefer slicing instead of reversed(digits[idx:])
return int(''.join(str(x) for x in digits))
class TestSuite(unittest.TestCase):
def test_next_bigger(self):
self.assertEqual(next_bigger(38276), 38627)
self.assertEqual(next_bigger(12345), 12354)
self.assertEqual(next_bigger(1528452), 1528524)
self.assertEqual(next_bigger(138654), 143568)
self.assertEqual(next_bigger(54321), -1)
self.assertEqual(next_bigger(999), -1)
self.assertEqual(next_bigger(5), -1)
if __name__ == '__main__':
unittest.main()
13.13 next perfect square¶
This program will look for the next perfect square. Check the argument to see if it is a perfect square itself, if it is not then return -1 otherwise look for the next perfect square for instance if you pass 121 then the script should return the next perfect square which is 144.
def find_next_square(sq):
root = sq ** 0.5
if root.is_integer():
return (root + 1)**2
return -1
# Another way:
def find_next_square2(sq):
x = sq**0.5
return -1 if x % 1 else (x+1)**2
13.14 nth digit¶
def find_nth_digit(n):
"""find the nth digit of given number.
1. find the length of the number where the nth digit is from.
2. find the actual number where the nth digit is from
3. find the nth digit and return
"""
length = 1
count = 9
start = 1
while n > length * count:
n -= length * count
length += 1
count *= 10
start *= 10
start += (n-1) / length
s = str(start)
return int(s[(n-1) % length])
13.15 prime check¶
def prime_check(n):
"""Return True if n is a prime number
Else return False.
"""
if n <= 1:
return False
if n == 2 or n == 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
j = 5
while j * j <= n:
if n % j == 0 or n % (j + 2) == 0:
return False
j += 6
return True
13.16 primes sieve of eratosthenes¶
Return list of all primes less than n, Using sieve of Eratosthenes.
Modification: We don't need to check all even numbers, we can make the sieve excluding even numbers and adding 2 to the primes list by default.
We are going to make an array of: x / 2 - 1 if number is even, else x / 2 (The -1 with even number it's to exclude the number itself) Because we just need numbers [from 3..x if x is odd]
# We can get value represented at index i with (i*2 + 3)
For example, for x = 10, we start with an array of x / 2 - 1 = 4 [1, 1, 1, 1]
3 5 7 9
For x = 11: [1, 1, 1, 1, 1]
3 5 7 9 11 # 11 is odd, it's included in the list
With this, we have reduced the array size to a half, and complexity it's also a half now.
def get_primes(n):
"""Return list of all primes less than n,
Using sieve of Eratosthenes.
"""
if n <= 0:
raise ValueError("'n' must be a positive integer.")
# If x is even, exclude x from list (-1):
sieve_size = (n // 2 - 1) if n % 2 == 0 else (n // 2)
sieve = [True for _ in range(sieve_size)] # Sieve
primes = [] # List of Primes
if n >= 2:
primes.append(2) # 2 is prime by default
for i in range(sieve_size):
if sieve[i]:
value_at_i = i*2 + 3
primes.append(value_at_i)
for j in range(i, sieve_size, value_at_i):
sieve[j] = False
return primes
13.17 pythagoras¶
input two of the three side in right angled triangle and return the third. use "?" to indicate the unknown side.
def pythagoras(opposite,adjacent,hypotenuse):
try:
if opposite == str("?"):
return ("Opposite = " + str(((hypotenuse**2) - (adjacent**2))**0.5))
elif adjacent == str("?"):
return ("Adjacent = " + str(((hypotenuse**2) - (opposite**2))**0.5))
elif hypotenuse == str("?"):
return ("Hypotenuse = " + str(((opposite**2) + (adjacent**2))**0.5))
else:
return "You already know the answer!"
except:
raise ValueError("invalid argument were given.")
13.18 rabin miller¶
Rabin-Miller primality test returning False implies that n is guaranteed composite returning True means that n is probably prime with a 4 ** -k chance of being wrong
import random
def is_prime(n, k):
def pow2_factor(num):
"""factor n into a power of 2 times an odd number"""
power = 0
while num % 2 == 0:
num /= 2
power += 1
return power, num
def valid_witness(a):
"""
returns true if a is a valid 'witness' for n
a valid witness increases chances of n being prime
an invalid witness guarantees n is composite
"""
x = pow(int(a), int(d), int(n))
if x == 1 or x == n - 1:
return False
for _ in range(r - 1):
x = pow(int(x), int(2), int(n))
if x == 1:
return True
if x == n - 1:
return False
return True
# precondition n >= 5
if n < 5:
return n == 2 or n == 3 # True for prime
r, d = pow2_factor(n - 1)
for _ in range(k):
if valid_witness(random.randrange(2, n - 2)):
return False
return True
13.19 rsa¶
RSA encryption algorithm a method for encrypting a number that uses seperate encryption and decryption keys this file only implements the key generation algorithm
there are three important numbers in RSA called n, e, and d e is called the encryption exponent d is called the decryption exponent n is called the modulus
these three numbers satisfy ((x ** e) ** d) % n == x % n
to use this system for encryption, n and e are made publicly available, and d is kept secret a number x can be encrypted by computing (x ** e) % n the original number can then be recovered by computing (E ** d) % n, where E is the encrypted number
fortunately, python provides a three argument version of pow() that can compute powers modulo a number very quickly: (a ** b) % c == pow(a,b,c)
import random
def generate_key(k, seed=None):
"""
the RSA key generating algorithm
k is the number of bits in n
"""
def modinv(a, m):
"""calculate the inverse of a mod m
that is, find b such that (a * b) % m == 1"""
b = 1
while not (a * b) % m == 1:
b += 1
return b
def gen_prime(k, seed=None):
"""generate a prime with k bits"""
def is_prime(num):
if num == 2:
return True
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
return False
return True
random.seed(seed)
while True:
key = random.randrange(int(2 ** (k - 1)), int(2 ** k))
if is_prime(key):
return key
# size in bits of p and q need to add up to the size of n
p_size = k / 2
q_size = k - p_size
e = gen_prime(k, seed) # in many cases, e is also chosen to be a small constant
while True:
p = gen_prime(p_size, seed)
if p % e != 1:
break
while True:
q = gen_prime(q_size, seed)
if q % e != 1:
break
n = p * q
l = (p - 1) * (q - 1) # calculate totient function
d = modinv(e, l)
return int(n), int(e), int(d)
def encrypt(data, e, n):
return pow(int(data), int(e), int(n))
def decrypt(data, d, n):
return pow(int(data), int(d), int(n))
# sample usage:
# n,e,d = generate_key(16)
# data = 20
# encrypted = pow(data,e,n)
# decrypted = pow(encrypted,d,n)
# assert decrypted == data
13.20 sqrt precision factor¶
Given a positive integer N and a precision factor P, it produces an output with a maximum error P from the actual square root of N.
Example: Given N = 5 and P = 0.001, can produce output x such that 2.235 < x < 2.237. Actual square root of 5 being 2.236.
def square_root(n, epsilon=0.001):
"""Return square root of n, with maximum absolute error epsilon"""
guess = n / 2
while abs(guess * guess - n) > epsilon:
guess = (guess + (n / guess)) / 2
return guess
13.21 summing digits¶
Recently, I encountered an interview question whose description was as below:
The number 89 is the first integer with more than one digit whose digits when raised up to consecutive powers give the same number. For example, 89 = 8**1 + 9**2 gives the number 89.
The next number after 89 with this property is 135 = 1**1 + 3**2 + 5**3 = 135.
Write a function that returns a list of numbers with the above property. The function will receive range as parameter.
def sum_dig_pow(a, b):
result = []
for number in range(a, b + 1):
exponent = 1 # set to 1
summation = 0 # set to 1
number_as_string = str(number)
tokens = list(map(int, number_as_string)) # parse the string into individual digits
for k in tokens:
summation = summation + (k ** exponent)
exponent += 1
if summation == number:
result.append(number)
return result
# Some test cases:
assert sum_dig_pow(1, 10) == [1, 2, 3, 4, 5, 6, 7, 8, 9]
assert sum_dig_pow(1, 100) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 89]
chapter 14: Matrix¶
14.1 bomb enemy¶
Given a 2D grid, each cell is either a wall 'W', an enemy 'E' or empty '0' (the number zero), return the maximum enemies you can kill using one bomb. The bomb kills all the enemies in the same row and column from the planted point until it hits the wall since the wall is too strong to be destroyed. Note that you can only put the bomb at an empty cell.
Example: For the given grid
0 E 0 0 E 0 W E 0 E 0 0
return 3. (Placing a bomb at (1,1) kills 3 enemies)
def max_killed_enemies(grid):
if not grid: return 0
m, n = len(grid), len(grid[0])
max_killed = 0
row_e, col_e = 0, [0] * n
# iterates over all cells in the grid
for i in range(m):
for j in range(n):
# makes sure we are next to a wall.
if j == 0 or grid[i][j-1] == 'W':
row_e = row_kills(grid, i, j)
# makes sure we are next to a wall.
if i == 0 or grid[i-1][j] == 'W':
col_e[j] = col_kills(grid, i, j)
# makes sure the cell contains a 0
if grid[i][j] == '0':
# updates the variable
max_killed = max(max_killed, row_e + col_e[j])
return max_killed
# calculate killed enemies for row i from column j
def row_kills(grid, i, j):
num = 0
len_row = len(grid[0])
while j < len_row and grid[i][j] != 'W':
if grid[i][j] == 'E':
num += 1
j += 1
return num
# calculate killed enemies for column j from row i
def col_kills(grid, i, j):
num = 0
len_col = len(grid)
while i < len_col and grid[i][j] != 'W':
if grid[i][j] == 'E':
num += 1
i += 1
return num
# ----------------- TESTS -------------------------
"""
Testsuite for the project
"""
import unittest
class TestBombEnemy(unittest.TestCase):
def test_3x4(self):
grid1 = [["0","E","0","0"],
["E","0","W","E"],
["0","E","0","0"]]
self.assertEqual(3,max_killed_enemies(grid1))
def test_4x4(self):
grid1 = [
["0", "E", "0", "E"],
["E", "E", "E", "0"],
["E", "0", "W", "E"],
["0", "E", "0", "0"]]
grid2 = [
["0", "0", "0", "E"],
["E", "0", "0", "0"],
["E", "0", "W", "E"],
["0", "E", "0", "0"]]
self.assertEqual(5,max_killed_enemies(grid1))
self.assertEqual(3,max_killed_enemies(grid2))
if __name__ == "__main__":
unittest.main()
14.2 copy transform¶
def rotate_clockwise(matrix):
new = []
for row in reversed(matrix):
for i, elem in enumerate(row):
try:
new[i].append(elem)
except IndexError:
new.insert(i, [])
new[i].append(elem)
return new
def rotate_counterclockwise(matrix):
new = []
for row in matrix:
for i, elem in enumerate(reversed(row)):
try:
new[i].append(elem)
except IndexError:
new.insert(i, [])
new[i].append(elem)
return new
def top_left_invert(matrix):
new = []
for row in matrix:
for i, elem in enumerate(row):
try:
new[i].append(elem)
except IndexError:
new.insert(i, [])
new[i].append(elem)
return new
def bottom_left_invert(matrix):
new = []
for row in reversed(matrix):
for i, elem in enumerate(reversed(row)):
try:
new[i].append(elem)
except IndexError:
new.insert(i, [])
new[i].append(elem)
return new
if __name__ == '__main__':
def print_matrix(matrix, name):
print('{}:\n['.format(name))
for row in matrix:
print(' {}'.format(row))
print(']\n')
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
print_matrix(matrix, 'initial')
print_matrix(rotate_clockwise(matrix), 'clockwise')
print_matrix(rotate_counterclockwise(matrix), 'counterclockwise')
print_matrix(top_left_invert(matrix), 'top left invert')
print_matrix(bottom_left_invert(matrix), 'bottom left invert')
14.3 count paths¶
# # Count the number of unique paths from a[0][0] to a[m-1][n-1] # We are allowed to move either right or down from a cell in the matrix. # Approaches- # (i) Recursion- Recurse starting from a[m-1][n-1], upwards and leftwards, # add the path count of both recursions and return count. # (ii) Dynamic Programming- Start from a[0][0].Store the count in a count # matrix. Return count[m-1][n-1] # T(n)- O(mn), S(n)- O(mn) #
def count_paths(m, n):
if m < 1 or n < 1:
return -1
count = [[None for j in range(n)] for i in range(m)]
# Taking care of the edge cases- matrix of size 1xn or mx1
for i in range(n):
count[0][i] = 1
for j in range(m):
count[j][0] = 1
for i in range(1, m):
for j in range(1, n):
# Number of ways to reach a[i][j] = number of ways to reach
# a[i-1][j] + a[i][j-1]
count[i][j] = count[i - 1][j] + count[i][j - 1]
print(count[m - 1][n - 1])
def main():
m, n = map(int, input('Enter two positive integers: ').split())
count_paths(m, n)
if __name__ == '__main__':
main()
14.4 crout matrix decomposition¶
Crout matrix decomposition is used to find two matrices that, when multiplied give our input matrix, so L * U = A. L stands for lower and L has non-zero elements only on diagonal and below. U stands for upper and U has non-zero elements only on diagonal and above.
This can for example be used to solve systems of linear equations. The last if is used if to avoid dividing by zero.
Example: We input the A matrix: [[1,2,3], [3,4,5], [6,7,8]]
We get: L = [1.0, 0.0, 0.0]
[3.0, -2.0, 0.0] [6.0, -5.0, 0.0]
- U = [1.0, 2.0, 3.0]
- [0.0, 1.0, 2.0] [0.0, 0.0, 1.0]
We can check that L * U = A.
I think the complexity should be O(n^3).
def crout_matrix_decomposition(A):
n = len(A)
L = [[0.0] * n for i in range(n)]
U = [[0.0] * n for i in range(n)]
for j in range(n):
U[j][j] = 1.0
for i in range(j, n):
alpha = float(A[i][j])
for k in range(j):
alpha -= L[i][k]*U[k][j]
L[i][j] = float(alpha)
for i in range(j+1, n):
tempU = float(A[j][i])
for k in range(j):
tempU -= float(L[j][k]*U[k][i])
if int(L[j][j]) == 0:
L[j][j] = float(0.1**40)
U[j][i] = float(tempU/L[j][j])
return (L,U)
14.5 rotate image¶
You are given an n x n 2D mat representing an image.
Rotate the image by 90 degrees (clockwise).
Follow up: Could you do this in-place?
# clockwise rotate
# first reverse up to down, then swap the symmetry
# 1 2 3 7 8 9 7 4 1
# 4 5 6 => 4 5 6 => 8 5 2
# 7 8 9 1 2 3 9 6 3
def rotate(mat):
if not mat:
return mat
mat.reverse()
for i in range(len(mat)):
for j in range(i):
mat[i][j], mat[j][i] = mat[j][i], mat[i][j]
if __name__ == "__main__":
mat = [[1,2,3],
[4,5,6],
[7,8,9]]
print(mat)
rotate(mat)
print(mat)
14.6 search in sorted matrix¶
# # Search a key in a row wise and column wise sorted (non-decreasing) matrix. # m- Number of rows in the matrix # n- Number of columns in the matrix # T(n)- O(m+n) #
def search_in_a_sorted_matrix(mat, m, n, key):
i, j = m-1, 0
while i >= 0 and j < n:
if key == mat[i][j]:
print ('Key %s found at row- %s column- %s' % (key, i+1, j+1))
return
if key < mat[i][j]:
i -= 1
else:
j += 1
print ('Key %s not found' % (key))
def main():
mat = [
[2, 5, 7],
[4, 8, 13],
[9, 11, 15],
[12, 17, 20]
]
key = 13
print (mat)
search_in_a_sorted_matrix(mat, len(mat), len(mat[0]), key)
if __name__ == '__main__':
main()
14.7 sparse dot vector¶
Suppose we have very large sparse vectors, which contains a lot of zeros and double .
find a data structure to store them get the dot product of them
def vector_to_index_value_list(vector):
return [(i, v) for i, v in enumerate(vector) if v != 0.0]
def dot_product(iv_list1, iv_list2):
product = 0
p1 = len(iv_list1) - 1
p2 = len(iv_list2) - 1
while p1 >= 0 and p2 >= 0:
i1, v1 = iv_list1[p1]
i2, v2 = iv_list2[p2]
if i1 < i2:
p1 -= 1
elif i2 < i1:
p2 -= 1
else:
product += v1 * v2
p1 -= 1
p2 -= 1
return product
def __test_simple():
print(dot_product(vector_to_index_value_list([1., 2., 3.]),
vector_to_index_value_list([0., 2., 2.])))
# 10
def __test_time():
vector_length = 1024
vector_count = 1024
nozero_counut = 10
def random_vector():
import random
vector = [0 for _ in range(vector_length)]
for i in random.sample(range(vector_length), nozero_counut):
vector[i] = random.random()
return vector
vectors = [random_vector() for _ in range(vector_count)]
iv_lists = [vector_to_index_value_list(vector) for vector in vectors]
import time
time_start = time.time()
for i in range(vector_count):
for j in range(i):
dot_product(iv_lists[i], iv_lists[j])
time_end = time.time()
print(time_end - time_start, 'seconds')
if __name__ == '__main__':
__test_simple()
__test_time()
14.8 sparse mul¶
Given two sparse matrices A and B, return the result of AB.
You may assume that A's column number is equal to B's row number.
Example:
- A = [
- [ 1, 0, 0], [-1, 0, 3]
]
- B = [
- [ 7, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 1 ]
]
1 0 0 | | 7 0 0 | | 7 0 0 |
- AB = | -1 0 3 | x | 0 0 0 | = | -7 0 3 |
- 0 0 1 |
# Python solution without table (~156ms):
def multiply(self, a, b):
"""
:type A: List[List[int]]
:type B: List[List[int]]
:rtype: List[List[int]]
"""
if a is None or b is None: return None
m, n, l = len(a), len(b[0]), len(b[0])
if len(b) != n:
raise Exception("A's column number must be equal to B's row number.")
c = [[0 for _ in range(l)] for _ in range(m)]
for i, row in enumerate(a):
for k, eleA in enumerate(row):
if eleA:
for j, eleB in enumerate(b[k]):
if eleB: c[i][j] += eleA * eleB
return c
# Python solution with only one table for B (~196ms):
def multiply(self, a, b):
"""
:type A: List[List[int]]
:type B: List[List[int]]
:rtype: List[List[int]]
"""
if a is None or b is None: return None
m, n, l = len(a), len(a[0]), len(b[0])
if len(b) != n:
raise Exception("A's column number must be equal to B's row number.")
c = [[0 for _ in range(l)] for _ in range(m)]
table_b = {}
for k, row in enumerate(b):
table_b[k] = {}
for j, eleB in enumerate(row):
if eleB: table_b[k][j] = eleB
for i, row in enumerate(a):
for k, eleA in enumerate(row):
if eleA:
for j, eleB in table_b[k].iteritems():
c[i][j] += eleA * eleB
return c
# Python solution with two tables (~196ms):
def multiply(self, a, b):
"""
:type A: List[List[int]]
:type B: List[List[int]]
:rtype: List[List[int]]
"""
if a is None or b is None: return None
m, n = len(a), len(b[0])
if len(b) != n:
raise Exception("A's column number must be equal to B's row number.")
l = len(b[0])
table_a, table_b = {}, {}
for i, row in enumerate(a):
for j, ele in enumerate(row):
if ele:
if i not in table_a: table_a[i] = {}
table_a[i][j] = ele
for i, row in enumerate(b):
for j, ele in enumerate(row):
if ele:
if i not in table_b: table_b[i] = {}
table_b[i][j] = ele
c = [[0 for j in range(l)] for i in range(m)]
for i in table_a:
for k in table_a[i]:
if k not in table_b: continue
for j in table_b[k]:
c[i][j] += table_a[i][k] * table_b[k][j]
return c
14.9 spiral traversal¶
Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. For example, Given the following matrix: [
[ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ]
]
You should return [1,2,3,6,9,8,7,4,5].
def spiral_traversal(matrix):
res = []
if len(matrix) == 0:
return res
row_begin = 0
row_end = len(matrix) - 1
col_begin = 0
col_end = len(matrix[0]) - 1
while row_begin <= row_end and col_begin <= col_end:
for i in range(col_begin, col_end+1):
res.append(matrix[row_begin][i])
row_begin += 1
for i in range(row_begin, row_end+1):
res.append(matrix[i][col_end])
col_end -= 1
if row_begin <= row_end:
for i in range(col_end, col_begin-1, -1):
res.append(matrix[row_end][i])
row_end -= 1
if col_begin <= col_end:
for i in range(row_end, row_begin-1, -1):
res.append(matrix[i][col_begin])
col_begin += 1
return res
if __name__ == "__main__":
mat = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(spiral_traversal(mat))
14.10 sudoku validator¶
Write a function validSolution/ValidateSolution/valid_solution() that accepts a 2D array representing a Sudoku board, and returns true if it is a valid solution, or false otherwise. The cells of the sudoku board may also contain 0's, which will represent empty cells. Boards containing one or more zeroes are considered to be invalid solutions. The board is always 9 cells by 9 cells, and every cell only contains integers from 0 to 9.
(More info at: http://en.wikipedia.org/wiki/Sudoku)
# Using dict/hash-table
from collections import defaultdict
def valid_solution_hashtable(board):
for i in range(len(board)):
dict_row = defaultdict(int)
dict_col = defaultdict(int)
for j in range(len(board[0])):
value_row = board[i][j]
value_col = board[j][i]
if not value_row or value_col == 0:
return False
if value_row in dict_row:
return False
else:
dict_row[value_row] += 1
if value_col in dict_col:
return False
else:
dict_col[value_col] += 1
for i in range(3):
for j in range(3):
grid_add = 0
for k in range(3):
for l in range(3):
grid_add += board[i*3+k][j*3+l]
if grid_add != 45:
return False
return True
# Without hash-table/dict
def valid_solution(board):
correct = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# check rows
for row in board:
if sorted(row) != correct:
return False
# check columns
for column in zip(*board):
if sorted(column) != correct:
return False
# check regions
for i in range(3):
for j in range(3):
region = []
for line in board[i*3:(i+1)*3]:
region += line[j*3:(j+1)*3]
if sorted(region) != correct:
return False
# if everything correct
return True
# Using set
def valid_solution_set (board):
valid = set(range(1, 10))
for row in board:
if set(row) != valid:
return False
for col in [[row[i] for row in board] for i in range(9)]:
if set(col) != valid:
return False
for x in range(3):
for y in range(3):
if set(sum([row[x*3:(x+1)*3] for row in board[y*3:(y+1)*3]], [])) != valid:
return False
return True
# test cases
# To avoid congestion I'll leave testing all the functions to the reader. Just change the name of the function in the below test cases.
import unittest
class TestSuite(unittest.TestCase):
def test_valid(self):
self.assertTrue(valid_solution([[5, 3, 4, 6, 7, 8, 9, 1, 2],
[6, 7, 2, 1, 9, 5, 3, 4, 8],
[1, 9, 8, 3, 4, 2, 5, 6, 7],
[8, 5, 9, 7, 6, 1, 4, 2, 3],
[4, 2, 6, 8, 5, 3, 7, 9, 1],
[7, 1, 3, 9, 2, 4, 8, 5, 6],
[9, 6, 1, 5, 3, 7, 2, 8, 4],
[2, 8, 7, 4, 1, 9, 6, 3, 5],
[3, 4, 5, 2, 8, 6, 1, 7, 9]]))
def test_invalid(self):
self.assertFalse(valid_solution([[5, 3, 4, 6, 7, 8, 9, 1, 2],
[6, 7, 2, 1, 9, 0, 3, 4, 9],
[1, 0, 0, 3, 4, 2, 5, 6, 0],
[8, 5, 9, 7, 6, 1, 0, 2, 0],
[4, 2, 6, 8, 5, 3, 7, 9, 1],
[7, 1, 3, 9, 2, 4, 8, 5, 6],
[9, 0, 1, 5, 3, 7, 2, 1, 4],
[2, 8, 7, 4, 1, 9, 6, 3, 5],
[3, 0, 0, 4, 8, 1, 1, 7, 9]]))
if __name__ == "__main__":
unittest.main()
chapter 15:ml¶
15.1 nearest neighbor¶
import math
def distance(x,y):
"""[summary]
HELPER-FUNCTION
calculates the (eulidean) distance between vector x and y.
Arguments:
x {[tuple]} -- [vector]
y {[tuple]} -- [vector]
"""
assert len(x) == len(y), "The vector must have same length"
result = ()
sum = 0
for i in range(len(x)):
result += (x[i] -y[i],)
for component in result:
sum += component**2
return math.sqrt(sum)
def nearest_neighbor(x, tSet):
"""[summary]
Implements the nearest neighbor algorithm
Arguments:
x {[tupel]} -- [vector]
tSet {[dict]} -- [training set]
Returns:
[type] -- [result of the AND-function]
"""
assert isinstance(x, tuple) and isinstance(tSet, dict)
current_key = ()
min_d = float('inf')
for key in tSet:
d = distance(x, key)
if d < min_d:
min_d = d
current_key = key
return tSet[current_key]
chapter 16: queues¶
16.1 max sliding window¶
Given an array and a number k Find the max elements of each of its sub-arrays of length k.
Keep indexes of good candidates in deque d. The indexes in d are from the current window, they're increasing, and their corresponding nums are decreasing. Then the first deque element is the index of the largest window value.
For each index i:
- Pop (from the end) indexes of smaller elements (they'll be useless).
- Append the current index.
- Pop (from the front) the index i - k, if it's still in the deque (it falls out of the window).
- If our window has reached size k, append the current window maximum to the output.
import collections
def max_sliding_window(arr, k):
qi = collections.deque() # queue storing indexes of elements
result = []
for i, n in enumerate(arr):
while qi and arr[qi[-1]] < n:
qi.pop()
qi.append(i)
if qi[0] == i - k:
qi.popleft()
if i >= k - 1:
result.append(arr[qi[0]])
return result
16.2 moving average¶
from __future__ import division
from collections import deque
class MovingAverage(object):
def __init__(self, size):
"""
Initialize your data structure here.
:type size: int
"""
self.queue = deque(maxlen=size)
def next(self, val):
"""
:type val: int
:rtype: float
"""
self.queue.append(val)
return sum(self.queue) / len(self.queue)
# Given a stream of integers and a window size,
# calculate the moving average of all integers in the sliding window.
if __name__ == '__main__':
m = MovingAverage(3)
assert m.next(1) == 1
assert m.next(10) == (1 + 10) / 2
assert m.next(3) == (1 + 10 + 3) / 3
assert m.next(5) == (10 + 3 + 5) / 3
16.3 priority queue¶
Implementation of priority queue using linear array. Insertion - O(n) Extract min/max Node - O(1)
import itertools
class PriorityQueueNode:
def __init__(self, data, priority):
self.data = data
self.priority = priority
def __repr__(self):
return "{}: {}".format(self.data, self.priority)
class PriorityQueue:
def __init__(self, items=None, priorities=None):
"""Create a priority queue with items (list or iterable).
If items is not passed, create empty priority queue."""
self.priority_queue_list = []
if items is None:
return
if priorities is None:
priorities = itertools.repeat(None)
for item, priority in zip(items, priorities):
self.push(item, priority=priority)
def __repr__(self):
return "PriorityQueue({!r})".format(self.priority_queue_list)
def size(self):
"""Return size of the priority queue.
"""
return len(self.priority_queue_list)
def push(self, item, priority=None):
"""Push the item in the priority queue.
if priority is not given, priority is set to the value of item.
"""
priority = item if priority is None else priority
node = PriorityQueueNode(item, priority)
for index, current in enumerate(self.priority_queue_list):
if current.priority < node.priority:
self.priority_queue_list.insert(index, node)
return
# when traversed complete queue
self.priority_queue_list.append(node)
def pop(self):
"""Remove and return the item with the lowest priority.
"""
# remove and return the first node from the queue
return self.priority_queue_list.pop().data
16.4 queue¶
Queue Abstract Data Type (ADT) * Queue() creates a new queue that is empty.
It needs no parameters and returns an empty queue.
- enqueue(item) adds a new item to the rear of the queue. It needs the item and returns nothing.
- dequeue() removes the front item from the queue. It needs no parameters and returns the item. The queue is modified.
- isEmpty() tests to see whether the queue is empty. It needs no parameters and returns a boolean value.
- size() returns the number of items in the queue. It needs no parameters and returns an integer.
- peek() returns the front element of the queue.
from abc import ABCMeta, abstractmethod
class AbstractQueue(metaclass=ABCMeta):
def __init__(self):
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
@abstractmethod
def enqueue(self, value):
pass
@abstractmethod
def dequeue(self):
pass
@abstractmethod
def peek(self):
pass
@abstractmethod
def __iter__(self):
pass
class ArrayQueue(AbstractQueue):
def __init__(self, capacity=10):
"""
Initialize python List with capacity of 10 or user given input.
Python List type is a dynamic array, so we have to restrict its
dynamic nature to make it work like a static array.
"""
super().__init__()
self._array = [None] * capacity
self._front = 0
self._rear = 0
def __iter__(self):
probe = self._front
while True:
if probe == self._rear:
return
yield self._array[probe]
probe += 1
def enqueue(self, value):
if self._rear == len(self._array):
self._expand()
self._array[self._rear] = value
self._rear += 1
self._size += 1
def dequeue(self):
if self.is_empty():
raise IndexError("Queue is empty")
value = self._array[self._front]
self._array[self._front] = None
self._front += 1
self._size -= 1
return value
def peek(self):
"""returns the front element of queue."""
if self.is_empty():
raise IndexError("Queue is empty")
return self._array[self._front]
def _expand(self):
"""expands size of the array.
Time Complexity: O(n)
"""
self._array += [None] * len(self._array)
class QueueNode:
def __init__(self, value):
self.value = value
self.next = None
class LinkedListQueue(AbstractQueue):
def __init__(self):
super().__init__()
self._front = None
self._rear = None
def __iter__(self):
probe = self._front
while True:
if probe is None:
return
yield probe.value
probe = probe.next
def enqueue(self, value):
node = QueueNode(value)
if self._front is None:
self._front = node
self._rear = node
else:
self._rear.next = node
self._rear = node
self._size += 1
def dequeue(self):
if self.is_empty():
raise IndexError("Queue is empty")
value = self._front.value
if self._front is self._rear:
self._front = None
self._rear = None
else:
self._front = self._front.next
self._size -= 1
return value
def peek(self):
"""returns the front element of queue."""
if self.is_empty():
raise IndexError("Queue is empty")
return self._front.value
16.5 reconstruct queue¶
# Suppose you have a random list of people standing in a queue. # Each person is described by a pair of integers (h, k), # where h is the height of the person and k is the number of people # in front of this person who have a height greater than or equal to h. # Write an algorithm to reconstruct the queue.
# Note: # The number of people is less than 1,100.
# Example
# Input: # [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
# Output: # [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
def reconstruct_queue(people):
"""
:type people: List[List[int]]
:rtype: List[List[int]]
"""
queue = []
people.sort(key=lambda x: (-x[0], x[1]))
for h, k in people:
queue.insert(k, [h, k])
return queue
16.6 zigzag iterator¶
class ZigZagIterator:
def __init__(self, v1, v2):
"""
Initialize your data structure here.
:type v1: List[int]
:type v2: List[int]
"""
self.queue=[_ for _ in (v1,v2) if _]
print(self.queue)
def next(self):
"""
:rtype: int
"""
v=self.queue.pop(0)
ret=v.pop(0)
if v: self.queue.append(v)
return ret
def has_next(self):
"""
:rtype: bool
"""
if self.queue: return True
return False
l1 = [1, 2]
l2 = [3, 4, 5, 6]
it = ZigZagIterator(l1, l2)
while it.has_next():
print(it.next())
chapter 17: search¶
17.1 binary search¶
# # Binary search works for a sorted array. # Note: The code logic is written for an array sorted in # increasing order. # T(n): O(log n) #
def binary_search(array, query):
lo, hi = 0, len(array) - 1
while lo <= hi:
mid = (hi + lo) // 2
val = array[mid]
if val == query:
return mid
elif val < query:
lo = mid + 1
else:
hi = mid - 1
return None
def binary_search_recur(array, low, high, val):
if low > high: # error case
return -1
mid = (low + high) // 2
if val < array[mid]:
return binary_search_recur(array, low, mid - 1, val)
elif val > array[mid]:
return binary_search_recur(array, mid + 1, high, val)
else:
return mid
17.2 find min rotate¶
Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).
Find the minimum element. The complexity must be O(logN)
You may assume no duplicate exists in the array.
def find_min_rotate(array):
low = 0
high = len(array) - 1
while low < high:
mid = (low + high) // 2
if array[mid] > array[high]:
low = mid + 1
else:
high = mid
return array[low]
def find_min_rotate_recur(array, low, high):
mid = (low + high) // 2
if mid == low:
return array[low]
elif array[mid] > array[high]:
return find_min_rotate_recur(array, mid + 1, high)
else:
return find_min_rotate_recur(array, low, mid)
17.3 firt occurrence¶
# # Find first occurance of a number in a sorted array (increasing order) # Approach- Binary Search # T(n)- O(log n) #
def first_occurrence(array, query):
lo, hi = 0, len(array) - 1
while lo <= hi:
mid = (lo + hi) // 2
#print("lo: ", lo, " hi: ", hi, " mid: ", mid)
if lo == hi:
break
if array[mid] < query:
lo = mid + 1
else:
hi = mid
if array[lo] == query:
return lo
17.4 jump search¶
import math
def jump_search(arr,target):
"""Jump Search
Worst-case Complexity: O(√n) (root(n))
All items in list must be sorted like binary search
Find block that contains target value and search it linearly in that block
It returns a first target value in array
reference: https://en.wikipedia.org/wiki/Jump_search
"""
n = len(arr)
block_size = int(math.sqrt(n))
block_prev = 0
block= block_size
# return -1 means that array doesn't contain taget value
# find block that contains target value
if arr[n - 1] < target:
return -1
while block <= n and arr[block - 1] < target:
block_prev = block
block += block_size
# find target value in block
while arr[block_prev] < target :
block_prev += 1
if block_prev == min(block, n) :
return -1
# if there is target value in array, return it
if arr[block_prev] == target :
return block_prev
else :
return -1
17.5 last occurrence¶
# # Find last occurance of a number in a sorted array (increasing order) # Approach- Binary Search # T(n)- O(log n) #
def last_occurrence(array, query):
lo, hi = 0, len(array) - 1
while lo <= hi:
mid = (hi + lo) // 2
if (array[mid] == query and mid == len(array)-1) or \
(array[mid] == query and array[mid+1] > query):
return mid
elif (array[mid] <= query):
lo = mid + 1
else:
hi = mid - 1
17.6 linear search¶
# # Linear search works in any array. # # T(n): O(n) #
def linear_search(array, query):
for i in range(len(array)):
if array[i] == query:
return i
return -1
17.7 next greatest letter¶
Given a list of sorted characters letters containing only lowercase letters, and given a target letter target, find the smallest element in the list that is larger than the given target.
Letters also wrap around. For example, if the target is target = 'z' and letters = ['a', 'b'], the answer is 'a'.
Input: letters = ["c", "f", "j"] target = "a" Output: "c"
Input: letters = ["c", "f", "j"] target = "c" Output: "f"
Input: letters = ["c", "f", "j"] target = "d" Output: "f"
Reference: https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/
import bisect
"""
Using bisect libarary
"""
def next_greatest_letter(letters, target):
index = bisect.bisect(letters, target)
return letters[index % len(letters)]
"""
Using binary search: complexity O(logN)
"""
def next_greatest_letter_v1(letters, target):
if letters[0] > target:
return letters[0]
if letters[len(letters) - 1] <= target:
return letters[0]
left, right = 0, len(letters) - 1
while left <= right:
mid = left + (right - left) // 2
if letters[mid] > target:
right = mid - 1
else:
left = mid + 1
return letters[left]
"""
Brute force: complexity O(N)
"""
def next_greatest_letter_v2(letters, target):
for index in letters:
if index > target:
return index
return letters[0]
17.8 search insert¶
Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.
For example: [1,3,5,6], 5 -> 2 [1,3,5,6], 2 -> 1 [1,3,5,6], 7 -> 4 [1,3,5,6], 0 -> 0
def search_insert(array, val):
low = 0
high = len(array) - 1
while low <= high:
mid = low + (high - low) // 2
if val > array[mid]:
low = mid + 1
else:
high = mid - 1
return low
17.9 search range¶
Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value. If the target is not found in the array, return [-1, -1].
For example: Input: nums = [5,7,7,8,8,8,10], target = 8 Output: [3,5] Input: nums = [5,7,7,8,8,8,10], target = 11 Output: [-1,-1]
def search_range(nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
low = 0
high = len(nums) - 1
while low <= high:
mid = low + (high - low) // 2
if target < nums[mid]:
high = mid - 1
elif target > nums[mid]:
low = mid + 1
else:
break
for j in range(len(nums) - 1, -1, -1):
if nums[j] == target:
return [mid, j]
return [-1, -1]
17.10 search rotate¶
Search in Rotated Sorted Array Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).
You are given a target value to search. If found in the array return its index, otherwise return -1.
Your algorithm's runtime complexity must be in the order of O(log n).¶
Explanation algorithm:
In classic binary search, we compare val with the midpoint to figure out if val belongs on the low or the high side. The complication here is that the array is rotated and may have an inflection point. Consider, for example:
Array1: [10, 15, 20, 0, 5] Array2: [50, 5, 20, 30, 40]
Note that both arrays have a midpoint of 20, but 5 appears on the left side of one and on the right side of the other. Therefore, comparing val with the midpoint is insufficient.
However, if we look a bit deeper, we can see that one half of the array must be ordered normally(increasing order). We can therefore look at the normally ordered half to determine whether we should search the low or hight side.
For example, if we are searching for 5 in Array1, we can look at the left element (10) and middle element (20). Since 10 < 20, the left half must be ordered normally. And, since 5 is not between those, we know that we must search the right half
In array2, we can see that since 50 > 20, the right half must be ordered normally. We turn to the middle 20, and right 40 element to check if 5 would fall between them. The value 5 would not Therefore, we search the left half.
There are 2 possible solution: iterative and recursion. Recursion helps you understand better the above algorithm explanation
def search_rotate(array, val):
low, high = 0, len(array) - 1
while low <= high:
mid = (low + high) // 2
if val == array[mid]:
return mid
if array[low] <= array[mid]:
if array[low] <= val <= array[mid]:
high = mid - 1
else:
low = mid + 1
else:
if array[mid] <= val <= array[high]:
low = mid + 1
else:
high = mid - 1
return -1
# Recursion technique
def search_rotate_recur(array, low, high, val):
if low >= high:
return -1
mid = (low + high) // 2
if val == array[mid]: # found element
return mid
if array[low] <= array[mid]:
if array[low] <= val <= array[mid]:
return search_rotate_recur(array, low, mid - 1, val) # Search left
else:
return search_rotate_recur(array, mid + 1, high, val) # Search right
else:
if array[mid] <= val <= array[high]:
return search_rotate_recur(array, mid + 1, high, val) # Search right
else:
return search_rotate_recur(array, low, mid - 1, val) # Search left
17.11 two sum¶
Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number. The function two_sum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based. You may assume that each input would have exactly one solution and you may not use the same element twice.
Input: numbers = [2, 7, 11, 15], target=9 Output: index1 = 1, index2 = 2
Solution: two_sum: using binary search two_sum1: using dictionary as a hash table two_sum2: using two pointers
# Using binary search technique
def two_sum(numbers, target):
for i in range(len(numbers)):
second_val = target - numbers[i]
low, high = i+1, len(numbers)-1
while low <= high:
mid = low + (high - low) // 2
if second_val == numbers[mid]:
return [i + 1, mid + 1]
elif second_val > numbers[mid]:
low = mid + 1
else:
high = mid - 1
# Using dictionary as a hash table
def two_sum1(numbers, target):
dic = {}
for i, num in enumerate(numbers):
if target - num in dic:
return [dic[target - num] + 1, i + 1]
dic[num] = i
# Using two pointers
def two_sum2(numbers, target):
p1 = 0 # pointer 1 holds from left of array numbers
p2 = len(numbers) - 1 # pointer 2 holds from right of array numbers
while p1 < p2:
s = numbers[p1] + numbers[p2]
if s == target:
return [p1 + 1, p2 + 1]
elif s > target:
p2 = p2 - 1
else:
p1 = p1 + 1
chapter 18: set¶
18.1 find keyboard row¶
Given a List of words, return the words that can be typed using letters of alphabet on only one row's of American keyboard.
For example: Input: ["Hello", "Alaska", "Dad", "Peace"] Output: ["Alaska", "Dad"]
Reference: https://leetcode.com/problems/keyboard-row/description/
def find_keyboard_row(words):
"""
:type words: List[str]
:rtype: List[str]
"""
keyboard = [
set('qwertyuiop'),
set('asdfghjkl'),
set('zxcvbnm'),
]
result = []
for word in words:
for key in keyboard:
if set(word.lower()).issubset(key):
result.append(word)
return result
18.2 randomized set¶
Design a data structure that supports all following operations in average O(1) time.
insert(val): Inserts an item val to the set if not already present. remove(val): Removes an item val from the set if present. random_element: Returns a random element from current set of elements.
Each element must have the same probability of being returned.
import random
class RandomizedSet():
"""
idea: shoot
"""
def __init__(self):
self.elements = []
self.index_map = {} # element -> index
def insert(self, new_one):
if new_one in self.index_map:
return
self.index_map[new_one] = len(self.elements)
self.elements.append(new_one)
def remove(self, old_one):
if not old_one in self.index_map:
return
index = self.index_map[old_one]
last = self.elements.pop()
self.index_map.pop(old_one)
if index == len(self.elements):
return
self.elements[index] = last
self.index_map[last] = index
def random_element(self):
return random.choice(self.elements)
def __test():
rset = RandomizedSet()
ground_truth = set()
n = 64
for i in range(n):
rset.insert(i)
ground_truth.add(i)
# Remove a half
for i in random.sample(range(n), n // 2):
rset.remove(i)
ground_truth.remove(i)
print(len(ground_truth), len(rset.elements), len(rset.index_map))
for i in ground_truth:
assert(i == rset.elements[rset.index_map[i]])
for i in range(n):
print(rset.random_element(), end=' ')
print()
if __name__ == "__main__":
__test()
18.3 set covering¶
Universe U of n elements Collection of subsets of U:
S = S1,S2...,Sm Where every substet Si has an associated cost.
Find a minimum cost subcollection of S that covers all elements of U
- Example:
U = {1,2,3,4,5} S = {S1,S2,S3}
S1 = {4,1,3}, Cost(S1) = 5 S2 = {2,5}, Cost(S2) = 10 S3 = {1,4,3,2}, Cost(S3) = 3
- Output:
- Set cover = {S2, S3} Min Cost = 13
def powerset(iterable):
"""Calculate the powerset of any iterable.
For a range of integers up to the length of the given list,
make all possible combinations and chain them together as one object.
From https://docs.python.org/3/library/itertools.html#itertools-recipes
"""
"list(powerset([1,2,3])) --> [(), (1,), (2,), (3,), (1,2), (1,3), (2,3), (1,2,3)]"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))
def optimal_set_cover(universe, subsets, costs):
""" Optimal algorithm - DONT USE ON BIG INPUTS - O(2^n) complexity!
Finds the minimum cost subcollection os S that covers all elements of U
Args:
universe (list): Universe of elements
subsets (dict): Subsets of U {S1:elements,S2:elements}
costs (dict): Costs of each subset in S - {S1:cost, S2:cost...}
"""
pset = powerset(subsets.keys())
best_set = None
best_cost = float("inf")
for subset in pset:
covered = set()
cost = 0
for s in subset:
covered.update(subsets[s])
cost += costs[s]
if len(covered) == len(universe) and cost < best_cost:
best_set = subset
best_cost = cost
return best_set
def greedy_set_cover(universe, subsets, costs):
"""Approximate greedy algorithm for set-covering. Can be used on large
inputs - though not an optimal solution.
Args:
universe (list): Universe of elements
subsets (dict): Subsets of U {S1:elements,S2:elements}
costs (dict): Costs of each subset in S - {S1:cost, S2:cost...}
"""
elements = set(e for s in subsets.keys() for e in subsets[s])
# elements don't cover universe -> invalid input for set cover
if elements != universe:
return None
# track elements of universe covered
covered = set()
cover_sets = []
while covered != universe:
min_cost_elem_ratio = float("inf")
min_set = None
# find set with minimum cost:elements_added ratio
for s, elements in subsets.items():
new_elements = len(elements - covered)
# set may have same elements as already covered -> new_elements = 0
# check to avoid division by 0 error
if new_elements != 0:
cost_elem_ratio = costs[s] / new_elements
if cost_elem_ratio < min_cost_elem_ratio:
min_cost_elem_ratio = cost_elem_ratio
min_set = s
cover_sets.append(min_set)
# union
covered |= subsets[min_set]
return cover_sets
if __name__ == '__main__':
universe = {1, 2, 3, 4, 5}
subsets = {'S1': {4, 1, 3}, 'S2': {2, 5}, 'S3': {1, 4, 3, 2}}
costs = {'S1': 5, 'S2': 10, 'S3': 3}
optimal_cover = optimal_set_cover(universe, subsets, costs)
optimal_cost = sum(costs[s] for s in optimal_cover)
greedy_cover = greedy_set_cover(universe, subsets, costs)
greedy_cost = sum(costs[s] for s in greedy_cover)
print('Optimal Set Cover:')
print(optimal_cover)
print('Cost = %s' % optimal_cost)
print('Greedy Set Cover:')
print(greedy_cover)
print('Cost = %s' % greedy_cost)
chapter 19: stack¶
19.1 is consecutive¶
Given a stack, a function is_consecutive takes a stack as a parameter and that returns whether or not the stack contains a sequence of consecutive integers starting from the bottom of the stack (returning true if it does, returning false if it does not).
For example: bottom [3, 4, 5, 6, 7] top Then the call of is_consecutive(s) should return true. bottom [3, 4, 6, 7] top Then the call of is_consecutive(s) should return false. bottom [3, 2, 1] top The function should return false due to reverse order.
Note: There are 2 solutions: first_is_consecutive: it uses a single stack as auxiliary storage second_is_consecutive: it uses a single queue as auxiliary storage
import collections
def first_is_consecutive(stack):
storage_stack = []
for i in range(len(stack)):
first_value = stack.pop()
if len(stack) == 0: # Case odd number of values in stack
return True
second_value = stack.pop()
if first_value - second_value != 1: # Not consecutive
return False
stack.append(second_value) # Backup second value
storage_stack.append(first_value)
# Back up stack from storage stack
for i in range(len(storage_stack)):
stack.append(storage_stack.pop())
return True
def second_is_consecutive(stack):
q = collections.deque()
for i in range(len(stack)):
first_value = stack.pop()
if len(stack) == 0: # Case odd number of values in stack
return True
second_value = stack.pop()
if first_value - second_value != 1: # Not consecutive
return False
stack.append(second_value) # Backup second value
q.append(first_value)
# Back up stack from queue
for i in range(len(q)):
stack.append(q.pop())
for i in range(len(stack)):
q.append(stack.pop())
for i in range(len(q)):
stack.append(q.pop())
return True
19.2 is sorted¶
Given a stack, a function is_sorted accepts a stack as a parameter and returns true if the elements in the stack occur in ascending increasing order from bottom, and false otherwise. That is, the smallest element should be at bottom
For example: bottom [6, 3, 5, 1, 2, 4] top The function should return false bottom [1, 2, 3, 4, 5, 6] top The function should return true
def is_sorted(stack):
storage_stack = []
for i in range(len(stack)):
if len(stack) == 0:
break
first_val = stack.pop()
if len(stack) == 0:
break
second_val = stack.pop()
if first_val < second_val:
return False
storage_stack.append(first_val)
stack.append(second_val)
# Backup stack
for i in range(len(storage_stack)):
stack.append(storage_stack.pop())
return True
19.3 longest abs path¶
# def lengthLongestPath(input):
# maxlen = 0
# pathlen = {0: 0}
# for line in input.splitlines():
# print("---------------")
# print("line:", line)
# name = line.strip('\t')
# print("name:", name)
# depth = len(line) - len(name)
# print("depth:", depth)
# if '.' in name:
# maxlen = max(maxlen, pathlen[depth] + len(name))
# else:
# pathlen[depth + 1] = pathlen[depth] + len(name) + 1
# print("maxlen:", maxlen)
# return maxlen
# def lengthLongestPath(input):
# paths = input.split("\n")
# level = [0] * 10
# maxLength = 0
# for path in paths:
# print("-------------")
# levelIdx = path.rfind("\t")
# print("Path: ", path)
# print("path.rfind(\\t)", path.rfind("\t"))
# print("levelIdx: ", levelIdx)
# print("level: ", level)
# level[levelIdx + 1] = level[levelIdx] + len(path) - levelIdx + 1
# print("level: ", level)
# if "." in path:
# maxLength = max(maxLength, level[levelIdx+1] - 1)
# print("maxlen: ", maxLength)
# return maxLength
def length_longest_path(input):
"""
:type input: str
:rtype: int
"""
curr_len, max_len = 0, 0 # running length and max length
stack = [] # keep track of the name length
for s in input.split('\n'):
print("---------")
print("<path>:", s)
depth = s.count('\t') # the depth of current dir or file
print("depth: ", depth)
print("stack: ", stack)
print("curlen: ", curr_len)
while len(stack) > depth: # go back to the correct depth
curr_len -= stack.pop()
stack.append(len(s.strip('\t'))+1) # 1 is the length of '/'
curr_len += stack[-1] # increase current length
print("stack: ", stack)
print("curlen: ", curr_len)
if '.' in s: # update maxlen only when it is a file
max_len = max(max_len, curr_len-1) # -1 is to minus one '/'
return max_len
st= "dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdirectory1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext"
st2 = "a\n\tb1\n\t\tf1.txt\n\taaaaa\n\t\tf2.txt"
print("path:", st2)
print("answer:", length_longest_path(st2))
19.4 ordered stack¶
#The stack remains always ordered such that the highest value is at the top and the lowest at the bottom
class OrderedStack:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def push_t(self, item):
self.items.append(item)
def push(self, item): #push method to maintain order when pushing new elements
temp_stack = OrderedStack()
if self.is_empty() or item > self.peek():
self.push_t(item)
else:
while item < self.peek() and not self.is_empty():
temp_stack.push_t(self.pop())
self.push_t(item)
while not temp_stack.is_empty():
self.push_t(temp_stack.pop())
def pop(self):
if self.is_empty():
raise IndexError("Stack is empty")
return self.items.pop()
def peek(self):
return self.items[len(self.items) - 1]
def size(self):
return len(self.items)
19.5 remove min¶
Given a stack, a function remove_min accepts a stack as a parameter and removes the smallest value from the stack.
For example: bottom [2, 8, 3, -6, 7, 3] top After remove_min(stack): bottom [2, 8, 3, 7, 3] top
def remove_min(stack):
storage_stack = []
if len(stack) == 0: # Stack is empty
return stack
# Find the smallest value
min = stack.pop()
stack.append(min)
for i in range(len(stack)):
val = stack.pop()
if val <= min:
min = val
storage_stack.append(val)
# Back up stack and remove min value
for i in range(len(storage_stack)):
val = storage_stack.pop()
if val != min:
stack.append(val)
return stack
19.6 simplify path¶
Given an absolute path for a file (Unix-style), simplify it.
For example, path = "/home/", => "/home" path = "/a/./b/../../c/", => "/c"
- Did you consider the case where path = "/../"?
- In this case, you should return "/".
- Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/".
- In this case, you should ignore redundant slashes and return "/home/foo".
def simplify_path(path):
"""
:type path: str
:rtype: str
"""
skip = {'..', '.', ''}
stack = []
paths = path.split('/')
for tok in paths:
if tok == '..':
if stack:
stack.pop()
elif tok not in skip:
stack.append(tok)
return '/' + '/'.join(stack)
19.7 stack¶
Stack Abstract Data Type (ADT) Stack() creates a new stack that is empty.
It needs no parameters and returns an empty stack.
- push(item) adds a new item to the top of the stack.
- It needs the item and returns nothing.
- pop() removes the top item from the stack.
- It needs no parameters and returns the item. The stack is modified.
- peek() returns the top item from the stack but does not remove it.
- It needs no parameters. The stack is not modified.
- isEmpty() tests to see whether the stack is empty.
- It needs no parameters and returns a boolean value.
- size() returns the number of items on the stack.
- It needs no parameters and returns an integer.
from abc import ABCMeta, abstractmethod
class AbstractStack(metaclass=ABCMeta):
"""Abstract Class for Stacks."""
def __init__(self):
self._top = -1
def __len__(self):
return self._top + 1
def __str__(self):
result = " ".join(map(str, self))
return 'Top-> ' + result
def is_empty(self):
return self._top == -1
@abstractmethod
def __iter__(self):
pass
@abstractmethod
def push(self, value):
pass
@abstractmethod
def pop(self):
pass
@abstractmethod
def peek(self):
pass
class ArrayStack(AbstractStack):
def __init__(self, size=10):
"""
Initialize python List with size of 10 or user given input.
Python List type is a dynamic array, so we have to restrict its
dynamic nature to make it work like a static array.
"""
super().__init__()
self._array = [None] * size
def __iter__(self):
probe = self._top
while True:
if probe == -1:
return
yield self._array[probe]
probe -= 1
def push(self, value):
self._top += 1
if self._top == len(self._array):
self._expand()
self._array[self._top] = value
def pop(self):
if self.is_empty():
raise IndexError("stack is empty")
value = self._array[self._top]
self._top -= 1
return value
def peek(self):
"""returns the current top element of the stack."""
if self.is_empty():
raise IndexError("stack is empty")
return self._array[self._top]
def _expand(self):
"""
expands size of the array.
Time Complexity: O(n)
"""
self._array += [None] * len(self._array) # double the size of the array
class StackNode:
"""Represents a single stack node."""
def __init__(self, value):
self.value = value
self.next = None
class LinkedListStack(AbstractStack):
def __init__(self):
super().__init__()
self.head = None
def __iter__(self):
probe = self.head
while True:
if probe is None:
return
yield probe.value
probe = probe.next
def push(self, value):
node = StackNode(value)
node.next = self.head
self.head = node
self._top += 1
def pop(self):
if self.is_empty():
raise IndexError("Stack is empty")
value = self.head.value
self.head = self.head.next
self._top -= 1
return value
def peek(self):
if self.is_empty():
raise IndexError("Stack is empty")
return self.head.value
19.8 stutter¶
Given a stack, stutter takes a stack as a parameter and replaces every value in the stack with two occurrences of that value.
For example, suppose the stack stores these values: bottom [3, 7, 1, 14, 9] top Then the stack should store these values after the method terminates: bottom [3, 3, 7, 7, 1, 1, 14, 14, 9, 9] top
Note: There are 2 solutions: first_stutter: it uses a single stack as auxiliary storage second_stutter: it uses a single queue as auxiliary storage
import collections
def first_stutter(stack):
storage_stack = []
for i in range(len(stack)):
storage_stack.append(stack.pop())
for i in range(len(storage_stack)):
val = storage_stack.pop()
stack.append(val)
stack.append(val)
return stack
def second_stutter(stack):
q = collections.deque()
# Put all values into queue from stack
for i in range(len(stack)):
q.append(stack.pop())
# Put values back into stack from queue
for i in range(len(q)):
stack.append(q.pop())
# Now, stack is reverse, put all values into queue from stack
for i in range(len(stack)):
q.append(stack.pop())
# Put 2 times value into stack from queue
for i in range(len(q)):
val = q.pop()
stack.append(val)
stack.append(val)
return stack
19.9 swithch pairs¶
Given a stack, switch_pairs function takes a stack as a parameter and that switches successive pairs of numbers starting at the bottom of the stack.
For example, if the stack initially stores these values: bottom [3, 8, 17, 9, 1, 10] top Your function should switch the first pair (3, 8), the second pair (17, 9), ...: bottom [8, 3, 9, 17, 10, 1] top
if there are an odd number of values in the stack, the value at the top of the stack is not moved: For example: bottom [3, 8, 17, 9, 1] top It would again switch pairs of values, but the value at the top of the stack (1) would not be moved bottom [8, 3, 9, 17, 1] top
Note: There are 2 solutions: first_switch_pairs: it uses a single stack as auxiliary storage second_switch_pairs: it uses a single queue as auxiliary storage
import collections
def first_switch_pairs(stack):
storage_stack = []
for i in range(len(stack)):
storage_stack.append(stack.pop())
for i in range(len(storage_stack)):
if len(storage_stack) == 0:
break
first = storage_stack.pop()
if len(storage_stack) == 0: # case: odd number of values in stack
stack.append(first)
break
second = storage_stack.pop()
stack.append(second)
stack.append(first)
return stack
def second_switch_pairs(stack):
q = collections.deque()
# Put all values into queue from stack
for i in range(len(stack)):
q.append(stack.pop())
# Put values back into stack from queue
for i in range(len(q)):
stack.append(q.pop())
# Now, stack is reverse, put all values into queue from stack
for i in range(len(stack)):
q.append(stack.pop())
# Swap pairs by appending the 2nd value before appending 1st value
for i in range(len(q)):
if len(q) == 0:
break
first = q.pop()
if len(q) == 0: # case: odd number of values in stack
stack.append(first)
break
second = q.pop()
stack.append(second)
stack.append(first)
return stack
19.10 valid parenthesis¶
Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.
The brackets must close in the correct order, "()" and "()[]{}" are all valid but "(]" and "([)]" are not.
def is_valid(s: str) -> bool:
stack = []
dic = {")": "(",
"}": "{",
"]": "["}
for char in s:
if char in dic.values():
stack.append(char)
elif char in dic:
if not stack or dic[char] != stack.pop():
return False
return not stack
chapter 20: Strings¶
20.1 add binary¶
Given two binary strings, return their sum (also a binary string).
For example, a = "11" b = "1" Return "100".
def add_binary(a, b):
s = ""
c, i, j = 0, len(a)-1, len(b)-1
zero = ord('0')
while (i >= 0 or j >= 0 or c == 1):
if (i >= 0):
c += ord(a[i]) - zero
i -= 1
if (j >= 0):
c += ord(b[j]) - zero
j -= 1
s = chr(c % 2 + zero) + s
c //= 2
return s
20.2 atbash cipher¶
Atbash cipher is mapping the alphabet to it's reverse. So if we take "a" as it is the first letter, we change it to the last - z.
Example: Attack at dawn --> Zggzxp zg wzdm
Complexity: O(n)
def atbash(s):
translated = ""
for i in range(len(s)):
n = ord(s[i])
if s[i].isalpha():
if s[i].isupper():
x = n - ord('A')
translated += chr(ord('Z') - x)
if s[i].islower():
x = n - ord('a')
translated += chr(ord('z') - x)
else:
translated += s[i]
return translated
20.3 breaking bad¶
Given an api which returns an array of words and an array of symbols, display the word with their matched symbol surrounded by square brackets.
If the word string matches more than one symbol, then choose the one with longest length. (ex. 'Microsoft' matches 'i' and 'cro'):
Example: Words array: ['Amazon', 'Microsoft', 'Google'] Symbols: ['i', 'Am', 'cro', 'Na', 'le', 'abc']
Output: [Am]azon, Mi[cro]soft, Goog[le]
My solution(Wrong): (I sorted the symbols array in descending order of length and ran loop over words array to find a symbol match(using indexOf in javascript) which worked. But I didn't make it through the interview, I am guessing my solution was O(n^2) and they expected an efficient algorithm.
output: ['[Am]azon', 'Mi[cro]soft', 'Goog[le]', 'Amaz[o]n', 'Micr[o]s[o]ft', 'G[o][o]gle']
from functools import reduce
def match_symbol(words, symbols):
import re
combined = []
for s in symbols:
for c in words:
r = re.search(s, c)
if r:
combined.append(re.sub(s, "[{}]".format(s), c))
return combined
def match_symbol_1(words, symbols):
res = []
# reversely sort the symbols according to their lengths.
symbols = sorted(symbols, key=lambda _: len(_), reverse=True)
for word in words:
for symbol in symbols:
word_replaced = ''
# once match, append the `word_replaced` to res, process next word
if word.find(symbol) != -1:
word_replaced = word.replace(symbol, '[' + symbol + ']')
res.append(word_replaced)
break
# if this word matches no symbol, append it.
if word_replaced == '':
res.append(word)
return res
"""
Another approach is to use a Tree for the dictionary (the symbols), and then
match brute force. The complexity will depend on the dictionary;
if all are suffixes of the other, it will be n*m
(where m is the size of the dictionary). For example, in Python:
"""
class TreeNode:
def __init__(self):
self.c = dict()
self.sym = None
def bracket(words, symbols):
root = TreeNode()
for s in symbols:
t = root
for char in s:
if char not in t.c:
t.c[char] = TreeNode()
t = t.c[char]
t.sym = s
result = dict()
for word in words:
i = 0
symlist = list()
while i < len(word):
j, t = i, root
while j < len(word) and word[j] in t.c:
t = t.c[word[j]]
if t.sym is not None:
symlist.append((j + 1 - len(t.sym), j + 1, t.sym))
j += 1
i += 1
if len(symlist) > 0:
sym = reduce(lambda x, y: x if x[1] - x[0] >= y[1] - y[0] else y,
symlist)
result[word] = "{}[{}]{}".format(word[:sym[0]], sym[2],
word[sym[1]:])
return tuple(word if word not in result else result[word] for word in words)
20.4 caesar cipher¶
Julius Caesar protected his confidential information by encrypting it using a cipher. Caesar's cipher shifts each letter by a number of letters. If the shift takes you past the end of the alphabet, just rotate back to the front of the alphabet. In the case of a rotation by 3, w, x, y and z would map to z, a, b and c. Original alphabet: abcdefghijklmnopqrstuvwxyz Alphabet rotated +3: defghijklmnopqrstuvwxyzabc
def caesar_cipher(s, k):
result = ""
for char in s:
n = ord(char)
if 64 < n < 91:
n = ((n - 65 + k) % 26) + 65
if 96 < n < 123:
n = ((n - 97 + k) % 26) + 97
result = result + chr(n)
return result
20.5 contain string¶
Implement strStr().
Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
Example 1: Input: haystack = "hello", needle = "ll" Output: 2
Example 2: Input: haystack = "aaaaa", needle = "bba" Output: -1 Reference: https://leetcode.com/problems/implement-strstr/description/
def contain_string(haystack, needle):
if len(needle) == 0:
return 0
if len(needle) > len(haystack):
return -1
for i in range(len(haystack)):
if len(haystack) - i < len(needle):
return -1
if haystack[i:i+len(needle)] == needle:
return i
return -1
20.6 count binary substring¶
- Give a string s, count the number of non-empty (contiguous) substrings that have
- the same number of 0's and 1's, and all the 0's and all the 1's in these substrings are grouped consecutively.
Substrings that occur multiple times are counted the number of times they occur. Example 1: Input: "00110011" Output: 6 Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
Notice that some of these substrings repeat and are counted the number of times they occur.
Also, "00110011" is not a valid substring because all the 0's (and 1's) are not grouped together.
Example 2: Input: "10101" Output: 4 Explanation: There are 4 substrings: "10", "01", "10", "01" that have equal number of consecutive 1's and 0's. Reference: https://leetcode.com/problems/count-binary-substrings/description/
def count_binary_substring(s):
cur = 1
pre = 0
count = 0
for i in range(1, len(s)):
if s[i] != s[i - 1]:
count = count + min(pre, cur)
pre = cur
cur = 1
else:
cur = cur + 1
count = count + min(pre, cur)
return count
20.7 decode string¶
# Given an encoded string, return it's decoded string.
# The encoding rule is: k[encoded_string], where the encoded_string # inside the square brackets is being repeated exactly k times. # Note that k is guaranteed to be a positive integer.
# You may assume that the input string is always valid; No extra white spaces, # square brackets are well-formed, etc.
# Furthermore, you may assume that the original data does not contain any # digits and that digits are only for those repeat numbers, k. # For example, there won't be input like 3a or 2[4].
# Examples:
# s = "3[a]2[bc]", return "aaabcbc". # s = "3[a2[c]]", return "accaccacc". # s = "2[abc]3[cd]ef", return "abcabccdcdcdef".
def decode_string(s):
"""
:type s: str
:rtype: str
"""
stack = []; cur_num = 0; cur_string = ''
for c in s:
if c == '[':
stack.append((cur_string, cur_num))
cur_string = ''
cur_num = 0
elif c == ']':
prev_string, num = stack.pop()
cur_string = prev_string + num * cur_string
elif c.isdigit():
cur_num = cur_num*10 + int(c)
else:
cur_string += c
return cur_string
20.8 delete reocurring¶
QUESTION: Given a string as your input, delete any reoccurring character, and return the new string.
This is a Google warmup interview question that was asked duirng phone screening at my university.
# time complexity O(n)
def delete_reoccurring_characters(string):
seen_characters = set()
output_string = ''
for char in string:
if char not in seen_characters:
seen_characters.add(char)
output_string += char
return output_string
20.9 domain extractor¶
Write a function that when given a URL as a string, parses out just the domain name and returns it as a string.
Examples: domain_name("http://github.com/SaadBenn") == "github" domain_name("http://www.zombie-bites.com") == "zombie-bites" domain_name("https://www.cnet.com") == "cnet"
Note: The idea is not to use any built-in libraries such as re (regular expression) or urlparse except .split() built-in function
# Non pythonic way
def domain_name_1(url):
#grab only the non http(s) part
full_domain_name = url.split('//')[-1]
#grab the actual one depending on the len of the list
actual_domain = full_domain_name.split('.')
# case when www is in the url
if (len(actual_domain) > 2):
return actual_domain[1]
# case when www is not in the url
return actual_domain[0]
# pythonic one liner
def domain_name_2(url):
return url.split("//")[-1].split("www.")[-1].split(".")[0]
20.10 encode decode¶
esign an algorithm to encode a list of strings to a string. The encoded mystring is then sent over the network and is decoded back to the original list of strings.
# Implement the encode and decode methods.
def encode(strs):
"""Encodes a list of strings to a single string.
:type strs: List[str]
:rtype: str
"""
res = ''
for string in strs.split():
res += str(len(string)) + ":" + string
return res
def decode(s):
"""Decodes a single string to a list of strings.
:type s: str
:rtype: List[str]
"""
strs = []
i = 0
while i < len(s):
index = s.find(":", i)
size = int(s[i:index])
strs.append(s[index+1: index+1+size])
i = index+1+size
return strs
20.11 first unique char¶
Given a string, find the first non-repeating character in it and return it's index. If it doesn't exist, return -1.
For example: s = "leetcode" return 0.
s = "loveleetcode", return 2.
Reference: https://leetcode.com/problems/first-unique-character-in-a-string/description/
def first_unique_char(s):
"""
:type s: str
:rtype: int
"""
if (len(s) == 1):
return 0
ban = []
for i in range(len(s)):
if all(s[i] != s[k] for k in range(i + 1, len(s))) == True and s[i] not in ban:
return i
else:
ban.append(s[i])
return -1
20.12 fizz buzz¶
Wtite a function that returns an array containing the numbers from 1 to N, where N is the parametered value. N will never be less than 1.
Replace certain values however if any of the following conditions are met:
If the value is a multiple of 3: use the value 'Fizz' instead If the value is a multiple of 5: use the value 'Buzz' instead If the value is a multiple of 3 & 5: use the value 'FizzBuzz' instead """
""" There is no fancy algorithm to solve fizz buzz.
Iterate from 1 through n Use the mod operator to determine if the current iteration is divisible by: 3 and 5 -> 'FizzBuzz' 3 -> 'Fizz' 5 -> 'Buzz' else -> string of current iteration return the results Complexity:
Time: O(n) Space: O(n)
def fizzbuzz(n):
# Validate the input
if n < 1:
raise ValueError('n cannot be less than one')
if n is None:
raise TypeError('n cannot be None')
result = []
for i in range(1, n+1):
if i%3 == 0 and i%5 == 0:
result.append('FizzBuzz')
elif i%3 == 0:
result.append('Fizz')
elif i%5 == 0:
result.append('Buzz')
else:
result.append(i)
return result
# Alternative solution
def fizzbuzz_with_helper_func(n):
return [fb(m) for m in range(1,n+1)]
def fb(m):
r = (m % 3 == 0) * "Fizz" + (m % 5 == 0) * "Buzz"
return r if r != "" else m
20.13 group anagrams¶
Given an array of strings, group anagrams together.
For example, given: ["eat", "tea", "tan", "ate", "nat", "bat"], Return:
- [
- ["ate", "eat","tea"], ["nat","tan"], ["bat"]
]
def group_anagrams(strs):
d = {}
ans = []
k = 0
for str in strs:
sstr = ''.join(sorted(str))
if sstr not in d:
d[sstr] = k
k += 1
ans.append([])
ans[-1].append(str)
else:
ans[d[sstr]].append(str)
return ans
20.14 int to roman¶
Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to 3999.
def int_to_roman(num):
"""
:type num: int
:rtype: str
"""
m = ["", "M", "MM", "MMM"];
c = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"];
x = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
i = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
return m[num//1000] + c[(num%1000)//100] + x[(num%100)//10] + i[num%10];
20.15 is palindrome¶
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases. For example, "A man, a plan, a canal: Panama" is a palindrome. "race a car" is not a palindrome. Note: Have you consider that the string might be empty? This is a good question to ask during an interview. For the purpose of this problem, we define empty string as valid palindrome.
from string import ascii_letters
def is_palindrome(s):
"""
:type s: str
:rtype: bool
"""
i = 0
j = len(s)-1
while i < j:
while i < j and not s[i].isalnum():
i += 1
while i < j and not s[j].isalnum():
j -= 1
if s[i].lower() != s[j].lower():
return False
i, j = i+1, j-1
return True
"""
Here is a bunch of other variations of is_palindrome function.
Variation 1:
Find the reverse of the string and compare it with the original string
Variation 2:
Loop from the start to length/2 and check the first character and last character
and so on... for instance s[0] compared with s[n-1], s[1] == s[n-2]...
Variation 3:
Using stack idea.
Note: We are assuming that we are just checking a one word string. To check if a complete sentence
"""
def remove_punctuation(s):
"""
Remove punctuation, case sensitivity and spaces
"""
return "".join(i.lower() for i in s if i in ascii_letters)
# Variation 1
def string_reverse(s):
return s[::-1]
def is_palindrome_reverse(s):
s = remove_punctuation(s)
# can also get rid of the string_reverse function and just do this return s == s[::-1] in one line.
if (s == string_reverse(s)):
return True
return False
# Variation 2
def is_palindrome_two_pointer(s):
s = remove_punctuation(s)
for i in range(0, len(s)//2):
if (s[i] != s[len(s) - i - 1]):
return False
return True
# Variation 3
def is_palindrome_stack(s):
stack = []
s = remove_punctuation(s)
for i in range(len(s)//2, len(s)):
stack.append(s[i])
for i in range(0, len(s)//2):
if s[i] != stack.pop():
return False
return True
20.16 is rotated¶
Given two strings s1 and s2, determine if s2 is a rotated version of s1. For example, is_rotated("hello", "llohe") returns True is_rotated("hello", "helol") returns False
accepts two strings returns bool Reference: https://leetcode.com/problems/rotate-string/description/
def is_rotated(s1, s2):
if len(s1) == len(s2):
return s2 in s1 + s1
else:
return False
"""
Another solution: brutal force
Complexity: O(N^2)
"""
def is_rotated_v1(s1, s2):
if len(s1) != len(s2):
return False
if len(s1) == 0:
return True
for c in range(len(s1)):
if all(s1[(c + i) % len(s1)] == s2[i] for i in range(len(s1))):
return True
return False
20.17 judge circle¶
Initially, there is a Robot at position (0, 0). Given a sequence of its moves, judge if this robot makes a circle, which means it moves back to the original place.
The move sequence is represented by a string. And each move is represent by a character. The valid robot moves are R (Right), L (Left), U (Up) and D (down). The output should be true or false representing whether the robot makes a circle.
Example 1: Input: "UD" Output: true Example 2: Input: "LL" Output: false
def judge_circle(moves):
dict_moves = {
'U' : 0,
'D' : 0,
'R' : 0,
'L' : 0
}
for char in moves:
dict_moves[char] = dict_moves[char] + 1
return dict_moves['L'] == dict_moves['R'] and dict_moves['U'] == dict_moves['D']
20.18 license number¶
def license_number(key, k):
res, alnum = [], []
for char in key:
if char != "-":
alnum.append(char)
for i, char in enumerate(reversed(alnum)):
res.append(char)
if (i+1) % k == 0 and i != len(alnum)-1:
res.append("-")
return "".join(res[::-1])
20.19 longest common prefix¶
Write a function to find the longest common prefix string amongst an array of strings.
If there is no common prefix, return an empty string "".
Example 1: Input: ["flower","flow","flight"] Output: "fl"
Example 2: Input: ["dog","racecar","car"] Output: "" Explanation: There is no common prefix among the input strings.
Reference: https://leetcode.com/problems/longest-common-prefix/description/
First solution: Horizontal scanning
def common_prefix(s1, s2):
"Return prefix common of 2 strings"
if not s1 or not s2:
return ""
k = 0
while s1[k] == s2[k]:
k = k + 1
if k >= len(s1) or k >= len(s2):
return s1[0:k]
return s1[0:k]
def longest_common_prefix_v1(strs):
if not strs:
return ""
result = strs[0]
for i in range(len(strs)):
result = common_prefix(result, strs[i])
return result
Second solution: Vertical scanning
def longest_common_prefix_v2(strs):
if not strs:
return ""
for i in range(len(strs[0])):
for string in strs[1:]:
if i == len(string) or string[i] != strs[0][i]:
return strs[0][0:i]
return strs[0]
Third solution: Divide and Conquer
def longest_common_prefix_v3(strs):
if not strs:
return ""
return longest_common(strs, 0, len(strs) -1)
def longest_common(strs, left, right):
if left == right:
return strs[left]
mid = (left + right) // 2
lcp_left = longest_common(strs, left, mid)
lcp_right = longest_common(strs, mid + 1, right)
return common_prefix(lcp_left, lcp_right)
20.20 make sentence¶
For a given string and dictionary, how many sentences can you make from the string, such that all the words are contained in the dictionary.
eg: for given string -> "appletablet" "apple", "tablet" "applet", "able", "t" "apple", "table", "t" "app", "let", "able", "t"
"applet", {app, let, apple, t, applet} => 3 "thing", {"thing"} -> 1
count = 0
def make_sentence(str_piece, dictionaries):
global count
if len(str_piece) == 0:
return True
for i in range(0, len(str_piece)):
prefix, suffix = str_piece[0:i], str_piece[i:]
if prefix in dictionaries:
if suffix in dictionaries or make_sentence(suffix, dictionaries):
count += 1
return True
20.21 merge string checker¶
At a job interview, you are challenged to write an algorithm to check if a given string, s, can be formed from two other strings, part1 and part2. The restriction is that the characters in part1 and part2 are in the same order as in s. The interviewer gives you the following example and tells you to figure out the rest from the given test cases. 'codewars' is a merge from 'cdw' and 'oears': s: c o d e w a r s = codewars part1: c d w = cdw part2: o e a r s = oears
# Recursive Solution
def is_merge_recursive(s, part1, part2):
if not part1:
return s == part2
if not part2:
return s == part1
if not s:
return part1 + part2 == ''
if s[0] == part1[0] and is_merge_recursive(s[1:], part1[1:], part2):
return True
if s[0] == part2[0] and is_merge_recursive(s[1:], part1, part2[1:]):
return True
return False
# An iterative approach
def is_merge_iterative(s, part1, part2):
tuple_list = [(s, part1, part2)]
while tuple_list:
string, p1, p2 = tuple_list.pop()
if string:
if p1 and string[0] == p1[0]:
tuple_list.append((string[1:], p1[1:], p2))
if p2 and string[0] == p2[0]:
tuple_list.append((string[1:], p1, p2[1:]))
else:
if not p1 and not p2:
return True
return False
20.22 min distance¶
Given two words word1 and word2, find the minimum number of steps required to make word1 and word2 the same, where in each step you can delete one character in either string.
For example: Input: "sea", "eat" Output: 2 Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".
Reference: https://leetcode.com/problems/delete-operation-for-two-strings/description/
def min_distance(word1, word2):
return len(word1) + len(word2) - 2 * lcs(word1, word2, len(word1), len(word2))
def lcs(s1, s2, i, j):
"""
The length of longest common subsequence among the two given strings s1 and s2
"""
if i == 0 or j == 0:
return 0
elif s1[i - 1] == s2[j - 1]:
return 1 + lcs(s1, s2, i - 1, j - 1)
else:
return max(lcs(s1, s2, i - 1, j), lcs(s1, s2, i, j - 1))
# TODO: Using dynamic programming
20.23 multiply strings¶
Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2.
Note:
The length of both num1 and num2 is < 110. Both num1 and num2 contains only digits 0-9. Both num1 and num2 does not contain any leading zero. You must not use any built-in BigInteger library or convert the inputs to integer directly.
def multiply(num1:"str", num2:"str")->"str":
carry = 1
interm = []
zero = ord('0')
i_pos = 1
for i in reversed(num1):
j_pos = 1
add = 0
for j in reversed(num2):
mult = (ord(i)-zero) * (ord(j)-zero) * j_pos * i_pos
j_pos *= 10
add += mult
i_pos *= 10
interm.append(add)
return str(sum(interm))
if __name__ == "__main__":
print(multiply("1", "23"))
print(multiply("23", "23"))
print(multiply("100", "23"))
print(multiply("100", "10000"))
20.24 one edit distance¶
Given two strings S and T, determine if they are both one edit distance apart.
def is_one_edit(s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
if len(s) > len(t):
return is_one_edit(t, s)
if len(t) - len(s) > 1 or t == s:
return False
for i in range(len(s)):
if s[i] != t[i]:
return s[i+1:] == t[i+1:] or s[i:] == t[i+1:]
return True
def is_one_edit2(s, t):
l1, l2 = len(s), len(t)
if l1 > l2:
return is_one_edit2(t, s)
if len(t) - len(s) > 1 or t == s:
return False
for i in range(len(s)):
if s[i] != t[i]:
if l1 == l2:
s = s[:i]+t[i]+s[i+1:] # modify
else:
s = s[:i]+t[i]+s[i:] # insertion
break
return s == t or s == t[:-1]
20.25 rabin karp¶
# Following program is the python implementation of # Rabin Karp Algorithm
class RollingHash:
def __init__(self, text, size_word):
self.text = text
self.hash = 0
self.size_word = size_word
for i in range(0, size_word):
#ord maps the character to a number
#subtract out the ASCII value of "a" to start the indexing at zero
self.hash += (ord(self.text[i]) - ord("a")+1)*(26**(size_word - i -1))
#start index of current window
self.window_start = 0
#end of index window
self.window_end = size_word
def move_window(self):
if self.window_end <= len(self.text) - 1:
#remove left letter from hash value
self.hash -= (ord(self.text[self.window_start]) - ord("a")+1)*26**(self.size_word-1)
self.hash *= 26
self.hash += ord(self.text[self.window_end])- ord("a")+1
self.window_start += 1
self.window_end += 1
def window_text(self):
return self.text[self.window_start:self.window_end]
def rabin_karp(word, text):
if word == "" or text == "":
return None
if len(word) > len(text):
return None
rolling_hash = RollingHash(text, len(word))
word_hash = RollingHash(word, len(word))
#word_hash.move_window()
for i in range(len(text) - len(word) + 1):
if rolling_hash.hash == word_hash.hash:
if rolling_hash.window_text() == word:
return i
rolling_hash.move_window()
return None
20.26 repeat string¶
Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of it. If no such solution, return -1.
For example, with A = "abcd" and B = "cdabcdab".
Return 3, because by repeating A three times (“abcdabcdabcd”), B is a substring of it; and B is not a substring of A repeated two times ("abcdabcd").
Note: The length of A and B will be between 1 and 10000.
Reference: https://leetcode.com/problems/repeated-string-match/description/
def repeat_string(A, B):
count = 1
tmp = A
max_count = (len(B) / len(A)) + 1
while not(B in tmp):
tmp = tmp + A
if (count > max_count):
count = -1
break
count = count + 1
return count
20.27 repeat substring¶
Given a non-empty string check if it can be constructed by taking a substring of it and appending multiple copies of the substring together.
For example: Input: "abab" Output: True Explanation: It's the substring "ab" twice.
Input: "aba" Output: False
Input: "abcabcabcabc" Output: True Explanation: It's the substring "abc" four times.
Reference: https://leetcode.com/problems/repeated-substring-pattern/description/
def repeat_substring(s):
"""
:type s: str
:rtype: bool
"""
str = (s + s)[1:-1]
return s in str
20.28 reverse string¶
def recursive(s):
l = len(s)
if l < 2:
return s
return recursive(s[l//2:]) + recursive(s[:l//2])
def iterative(s):
r = list(s)
i, j = 0, len(s) - 1
while i < j:
r[i], r[j] = r[j], r[i]
i += 1
j -= 1
return "".join(r)
def pythonic(s):
r = list(reversed(s))
return "".join(r)
def ultra_pythonic(s):
return s[::-1]
20.29 reverse vowel¶
def reverse_vowel(s):
vowels = "AEIOUaeiou"
i, j = 0, len(s)-1
s = list(s)
while i < j:
while i < j and s[i] not in vowels:
i += 1
while i < j and s[j] not in vowels:
j -= 1
s[i], s[j] = s[j], s[i]
i, j = i + 1, j - 1
return "".join(s)
20.30 reverse words¶
def reverse(array, i, j):
while i < j:
array[i], array[j] = array[j], array[i]
i += 1
j -= 1
def reverse_words(string):
arr = string.strip().split() # arr is list of words
n = len(arr)
reverse(arr, 0, n-1)
return " ".join(arr)
if __name__ == "__main__":
test = "I am keon kim and I like pizza"
print(test)
print(reverse_words(test))
20.31 roman to int¶
Given a roman numeral, convert it to an integer. Input is guaranteed to be within the range from 1 to 3999.
def roman_to_int(s:"str")->"int":
number = 0
roman = {'M':1000, 'D':500, 'C': 100, 'L':50, 'X':10, 'V':5, 'I':1}
for i in range(len(s)-1):
if roman[s[i]] < roman[s[i+1]]:
number -= roman[s[i]]
else:
number += roman[s[i]]
return number + roman[s[-1]]
if __name__ == "__main__":
roman = "DCXXI"
print(roman_to_int(roman))
20.32 rotate¶
Given a strings s and int k, return a string that rotates k times
For example, rotate("hello", 2) return "llohe" rotate("hello", 5) return "hello" rotate("hello", 6) return "elloh" rotate("hello", 7) return "llohe"
accepts two strings returns bool
def rotate(s, k):
double_s = s + s
if k <= len(s):
return double_s[k:k + len(s)]
else:
return double_s[k-len(s):k]
20.33 strip url params¶
Write a function that does the following: Removes any duplicate query string parameters from the url Removes any query string parameters specified within the 2nd argument (optional array)
An example: www.saadbenn.com?a=1&b=2&a=2') // returns 'www.saadbenn.com?a=1&b=2'
from collections import defaultdict
import urllib
import urllib.parse
# Here is a very non-pythonic grotesque solution
def strip_url_params1(url, params_to_strip=None):
if not params_to_strip:
params_to_strip = []
if url:
result = '' # final result to be returned
tokens = url.split('?')
domain = tokens[0]
query_string = tokens[-1]
result += domain
# add the '?' to our result if it is in the url
if len(tokens) > 1:
result += '?'
if not query_string:
return url
else:
# logic for removing duplicate query strings
# build up the list by splitting the query_string using digits
key_value_string = []
string = ''
for char in query_string:
if char.isdigit():
key_value_string.append(string + char)
string = ''
else:
string += char
dict = defaultdict(int)
# logic for checking whether we should add the string to our result
for i in key_value_string:
_token = i.split('=')
if _token[0]:
length = len(_token[0])
if length == 1:
if _token and (not(_token[0] in dict)):
if params_to_strip:
if _token[0] != params_to_strip[0]:
dict[_token[0]] = _token[1]
result = result + _token[0] + '=' + _token[1]
else:
if not _token[0] in dict:
dict[_token[0]] = _token[1]
result = result + _token[0] + '=' + _token[1]
else:
check = _token[0]
letter = check[1]
if _token and (not(letter in dict)):
if params_to_strip:
if letter != params_to_strip[0]:
dict[letter] = _token[1]
result = result + _token[0] + '=' + _token[1]
else:
if not letter in dict:
dict[letter] = _token[1]
result = result + _token[0] + '=' + _token[1]
return result
# A very friendly pythonic solution (easy to follow)
def strip_url_params2(url, param_to_strip=[]):
if '?' not in url:
return url
queries = (url.split('?')[1]).split('&')
queries_obj = [query[0] for query in queries]
for i in range(len(queries_obj) - 1, 0, -1):
if queries_obj[i] in param_to_strip or queries_obj[i] in queries_obj[0:i]:
queries.pop(i)
return url.split('?')[0] + '?' + '&'.join(queries)
# Here is my friend's solution using python's builtin libraries
def strip_url_params3(url, strip=None):
if not strip: strip = []
parse = urllib.parse.urlparse(url)
query = urllib.parse.parse_qs(parse.query)
query = {k: v[0] for k, v in query.items() if k not in strip}
query = urllib.parse.urlencode(query)
new = parse._replace(query=query)
return new.geturl()
20.34 strong password¶
The signup page required her to input a name and a password. However, the password must be strong. The website considers a password to be strong if it satisfies the following criteria:
- Its length is at least 6.
- It contains at least one digit.
- It contains at least one lowercase English character.
- It contains at least one uppercase English character.
5) It contains at least one special character. The special characters are: !@#$%^&*()-+ She typed a random string of length in the password field but wasn't sure if it was strong. Given the string she typed, can you find the minimum number of characters she must add to make her password strong?
Note: Here's the set of types of characters in a form you can paste in your solution: numbers = "0123456789" lower_case = "abcdefghijklmnopqrstuvwxyz" upper_case = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" special_characters = "!@#$%^&*()-+"
Input Format The first line contains an integer denoting the length of the string. The second line contains a string consisting of characters, the password typed by Louise. Each character is either a lowercase/uppercase English alphabet, a digit, or a special character.
Sample Input 1: strong_password(3,"Ab1") Output: 3 (Because She can make the password strong by adding characters,for example, $hk, turning the password into Ab1$hk which is strong. 2 characters aren't enough since the length must be at least 6.)
Sample Output 2: strong_password(11,"#Algorithms") Output: 1 (Because the password isn't strong, but she can make it strong by adding a single digit.)
def strong_password(n, password):
count_error = 0
# Return the minimum number of characters to make the password strong
if any(i.isdigit() for i in password) == False:
count_error = count_error + 1
if any(i.islower() for i in password) == False:
count_error = count_error + 1
if any(i.isupper() for i in password) == False:
count_error = count_error + 1
if any(i in '!@#$%^&*()-+' for i in password) == False:
count_error = count_error + 1
return max(count_error, 6 - n)
20.35 text justification¶
Given an array of words and a width maxWidth, format the text such that each line has exactly maxWidth characters and is fully (left and right) justified.
You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces ' ' when necessary so that each line has exactly maxWidth characters.
Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line do not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right.
For the last line of text, it should be left justified and no extra space is inserted between words.
Note: A word is defined as a character sequence consisting of non-space characters only. Each word's length is guaranteed to be greater than 0 and not exceed maxWidth. The input array words contains at least one word.
Example: Input: words = ["What","must","be","acknowledgment","shall","be"] maxWidth = 16 Output: [
"What must be", "acknowledgment ", "shall be "
]
def text_justification(words, max_width):
'''
:type words: list
:type max_width: int
:rtype: list
'''
ret = [] # return value
row_len = 0 # current length of strs in a row
row_words = [] # current words in a row
index = 0 # the index of current word in words
is_first_word = True # is current word the first in a row
while index < len(words):
while row_len <= max_width and index < len(words):
if len(words[index]) > max_width:
raise ValueError("there exists word whose length is larger than max_width")
tmp = row_len
row_words.append(words[index])
tmp += len(words[index])
if not is_first_word:
tmp += 1 # except for the first word, each word should have at least a ' ' before it.
if tmp > max_width:
row_words.pop()
break
row_len = tmp
index += 1
is_first_word = False
# here we have already got a row of str , then we should supplement enough ' ' to make sure the length is max_width.
row = ""
# if the row is the last
if index == len(words):
for word in row_words:
row += (word + ' ')
row = row[:-1]
row += ' ' * (max_width - len(row))
# not the last row and more than one word
elif len(row_words) != 1:
space_num = max_width - row_len
space_num_of_each_interval = space_num // (len(row_words) - 1)
space_num_rest = space_num - space_num_of_each_interval * (len(row_words) - 1)
for j in range(len(row_words)):
row += row_words[j]
if j != len(row_words) - 1:
row += ' ' * (1 + space_num_of_each_interval)
if space_num_rest > 0:
row += ' '
space_num_rest -= 1
# row with only one word
else:
row += row_words[0]
row += ' ' * (max_width - len(row))
ret.append(row)
# after a row , reset those value
row_len = 0
row_words = []
is_first_word = True
return ret
20.36 unique morse¶
International Morse Code defines a standard encoding where each letter is mapped to a series of dots and dashes, as follows: "a" maps to ".-", "b" maps to "-...", "c" maps to "-.-.", and so on.
- For convenience, the full table for the 26 letters of the English alphabet is given below:
- 'a':".-", 'b':"-...", 'c':"-.-.", 'd': "-..", 'e':".", 'f':"..-.", 'g':"--.", 'h':"....", 'i':"..", 'j':".---", 'k':"-.-", 'l':".-..", 'm':"--", 'n':"-.", 'o':"---", 'p':".--.", 'q':"--.-", 'r':".-.", 's':"...", 't':"-", 'u':"..-", 'v':"...-", 'w':".--", 'x':"-..-", 'y':"-.--", 'z':"--.."
Now, given a list of words, each word can be written as a concatenation of the Morse code of each letter. For example, "cab" can be written as "-.-.-....-", (which is the concatenation "-.-." + "-..." + ".-"). We'll call such a concatenation, the transformation of a word.
Return the number of different transformations among all words we have. Example: Input: words = ["gin", "zen", "gig", "msg"] Output: 2 Explanation: The transformation of each word is: "gin" -> "--...-." "zen" -> "--...-." "gig" -> "--...--." "msg" -> "--...--."
There are 2 different transformations, "--...-." and "--...--.".
morse_code = {
'a':".-",
'b':"-...",
'c':"-.-.",
'd': "-..",
'e':".",
'f':"..-.",
'g':"--.",
'h':"....",
'i':"..",
'j':".---",
'k':"-.-",
'l':".-..",
'm':"--",
'n':"-.",
'o':"---",
'p':".--.",
'q':"--.-",
'r':".-.",
's':"...",
't':"-",
'u':"..-",
'v':"...-",
'w':".--",
'x':"-..-",
'y':"-.--",
'z':"--.."
}
def convert_morse_word(word):
morse_word = ""
word = word.lower()
for char in word:
morse_word = morse_word + morse_code[char]
return morse_word
def unique_morse(words):
unique_morse_word = []
for word in words:
morse_word = convert_morse_word(word)
if morse_word not in unique_morse_word:
unique_morse_word.append(morse_word)
return len(unique_morse_word)
20.37 validate coordinates¶
Create a function that will validate if given parameters are valid geographical coordinates. Valid coordinates look like the following: "23.32353342, -32.543534534". The return value should be either true or false. Latitude (which is first float) can be between 0 and 90, positive or negative. Longitude (which is second float) can be between 0 and 180, positive or negative. Coordinates can only contain digits, or one of the following symbols (including space after comma) -, . There should be no space between the minus "-" sign and the digit after it.
Here are some valid coordinates: -23, 25 43.91343345, 143 4, -3
And some invalid ones: 23.234, - 23.4234 N23.43345, E32.6457 6.325624, 43.34345.345 0, 1,2
# I'll be adding my attempt as well as my friend's solution (took us ~ 1 hour)
# my attempt
import re
def is_valid_coordinates_0(coordinates):
for char in coordinates:
if not (char.isdigit() or char in ['-', '.', ',', ' ']):
return False
l = coordinates.split(", ")
if len(l) != 2:
return False
try:
latitude = float(l[0])
longitude = float(l[1])
except:
return False
return -90 <= latitude <= 90 and -180 <= longitude <= 180
# friends solutions
def is_valid_coordinates_1(coordinates):
try:
lat, lng = [abs(float(c)) for c in coordinates.split(',') if 'e' not in c]
except ValueError:
return False
return lat <= 90 and lng <= 180
# using regular expression
def is_valid_coordinates_regular_expression(coordinates):
return bool(re.match("-?(\d|[1-8]\d|90)\.?\d*, -?(\d|[1-9]\d|1[0-7]\d|180)\.?\d*$", coordinates))
20.38 word squares¶
# Given a set of words (without duplicates), # find all word squares you can build from them.
# A sequence of words forms a valid word square # if the kth row and column read the exact same string, # where 0 ≤ k < max(numRows, numColumns).
# For example, the word sequence ["ball","area","lead","lady"] forms # a word square because each word reads the same both horizontally # and vertically.
# b a l l # a r e a # l e a d # l a d y # Note: # There are at least 1 and at most 1000 words. # All words will have the exact same length. # Word length is at least 1 and at most 5. # Each word contains only lowercase English alphabet a-z.
# Example 1:
# Input: # ["area","lead","wall","lady","ball"]
# Output: # [
- # [ "wall",
- # "area", # "lead", # "lady"
# ], # [ "ball",
# "area", # "lead", # "lady"# ]
# ]
# Explanation: # The output consists of two word squares. The order of output does not matter # (just the order of words in each word square matters).
import collections
def word_squares(words):
n = len(words[0])
fulls = collections.defaultdict(list)
for word in words:
for i in range(n):
fulls[word[:i]].append(word)
def build(square):
if len(square) == n:
squares.append(square)
return
prefix = ""
for k in range(len(square)):
prefix += square[k][len(square)]
for word in fulls[prefix]:
build(square + [word])
squares = []
for word in words:
build([word])
return squares
chapter 21: tree¶
21.1 avl¶
from tree.tree import TreeNode
class AvlTree(object):
"""
An avl tree.
"""
def __init__(self):
# Root node of the tree.
self.node = None
self.height = -1
self.balance = 0
def insert(self, key):
"""
Insert new key into node
"""
# Create new node
n = TreeNode(key)
if not self.node:
self.node = n
self.node.left = AvlTree()
self.node.right = AvlTree()
elif key < self.node.val:
self.node.left.insert(key)
elif key > self.node.val:
self.node.right.insert(key)
self.re_balance()
def re_balance(self):
"""
Re balance tree. After inserting or deleting a node,
"""
self.update_heights(recursive=False)
self.update_balances(False)
while self.balance < -1 or self.balance > 1:
if self.balance > 1:
if self.node.left.balance < 0:
self.node.left.rotate_left()
self.update_heights()
self.update_balances()
self.rotate_right()
self.update_heights()
self.update_balances()
if self.balance < -1:
if self.node.right.balance > 0:
self.node.right.rotate_right()
self.update_heights()
self.update_balances()
self.rotate_left()
self.update_heights()
self.update_balances()
def update_heights(self, recursive=True):
"""
Update tree height
"""
if self.node:
if recursive:
if self.node.left:
self.node.left.update_heights()
if self.node.right:
self.node.right.update_heights()
self.height = 1 + max(self.node.left.height, self.node.right.height)
else:
self.height = -1
def update_balances(self, recursive=True):
"""
Calculate tree balance factor
"""
if self.node:
if recursive:
if self.node.left:
self.node.left.update_balances()
if self.node.right:
self.node.right.update_balances()
self.balance = self.node.left.height - self.node.right.height
else:
self.balance = 0
def rotate_right(self):
"""
Right rotation
"""
new_root = self.node.left.node
new_left_sub = new_root.right.node
old_root = self.node
self.node = new_root
old_root.left.node = new_left_sub
new_root.right.node = old_root
def rotate_left(self):
"""
Left rotation
"""
new_root = self.node.right.node
new_left_sub = new_root.left.node
old_root = self.node
self.node = new_root
old_root.right.node = new_left_sub
new_root.left.node = old_root
def in_order_traverse(self):
"""
In-order traversal of the tree
"""
result = []
if not self.node:
return result
result.extend(self.node.left.in_order_traverse())
result.append(self.node.key)
result.extend(self.node.right.in_order_traverse())
return result
21.2 bst¶
bst - array to bst¶
Given an array where elements are sorted in ascending order, convert it to a height balanced BST.
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def array_to_bst(nums):
if not nums:
return None
mid = len(nums)//2
node = TreeNode(nums[mid])
node.left = array_to_bst(nums[:mid])
node.right = array_to_bst(nums[mid+1:])
return node
bst - bst closest value¶
# Given a non-empty binary search tree and a target value, # find the value in the BST that is closest to the target.
# Note: # Given target value is a floating point. # You are guaranteed to have only one unique value in the BST # that is closest to the target.
# Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val = x # self.left = None # self.right = None
def closest_value(root, target):
"""
:type root: TreeNode
:type target: float
:rtype: int
"""
a = root.val
kid = root.left if target < a else root.right
if not kid:
return a
b = closest_value(kid, target)
return min((a,b), key=lambda x: abs(target-x))
bst - bst¶
Implement Binary Search Tree. It has method: 1. Insert 2. Search 3. Size 4. Traversal (Preorder, Inorder, Postorder)
import unittest
class Node(object):
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BST(object):
def __init__(self):
self.root = None
def get_root(self):
return self.root
"""
Get the number of elements
Using recursion. Complexity O(logN)
"""
def size(self):
return self.recur_size(self.root)
def recur_size(self, root):
if root is None:
return 0
else:
return 1 + self.recur_size(root.left) + self.recur_size(root.right)
"""
Search data in bst
Using recursion. Complexity O(logN)
"""
def search(self, data):
return self.recur_search(self.root, data)
def recur_search(self, root, data):
if root is None:
return False
if root.data == data:
return True
elif data > root.data: # Go to right root
return self.recur_search(root.right, data)
else: # Go to left root
return self.recur_search(root.left, data)
"""
Insert data in bst
Using recursion. Complexity O(logN)
"""
def insert(self, data):
if self.root:
return self.recur_insert(self.root, data)
else:
self.root = Node(data)
return True
def recur_insert(self, root, data):
if root.data == data: # The data is already there
return False
elif data < root.data: # Go to left root
if root.left: # If left root is a node
return self.recur_insert(root.left, data)
else: # left root is a None
root.left = Node(data)
return True
else: # Go to right root
if root.right: # If right root is a node
return self.recur_insert(root.right, data)
else:
root.right = Node(data)
return True
"""
Preorder, Postorder, Inorder traversal bst
"""
def preorder(self, root):
if root:
print(str(root.data), end = ' ')
self.preorder(root.left)
self.preorder(root.right)
def inorder(self, root):
if root:
self.inorder(root.left)
print(str(root.data), end = ' ')
self.inorder(root.right)
def postorder(self, root):
if root:
self.postorder(root.left)
self.postorder(root.right)
print(str(root.data), end = ' ')
"""
The tree is created for testing:
10
/ \
6 15
/ \ / \
4 9 12 24
/ / \
7 20 30
/
18
"""
class TestSuite(unittest.TestCase):
def setUp(self):
self.tree = BST()
self.tree.insert(10)
self.tree.insert(15)
self.tree.insert(6)
self.tree.insert(4)
self.tree.insert(9)
self.tree.insert(12)
self.tree.insert(24)
self.tree.insert(7)
self.tree.insert(20)
self.tree.insert(30)
self.tree.insert(18)
def test_search(self):
self.assertTrue(self.tree.search(24))
self.assertFalse(self.tree.search(50))
def test_size(self):
self.assertEqual(11, self.tree.size())
if __name__ == '__main__':
unittest.main()
bst - BSTiterator¶
class BSTIterator:
def __init__(self, root):
self.stack = []
while root:
self.stack.append(root)
root = root.left
def has_next(self):
return bool(self.stack)
def next(self):
node = self.stack.pop()
tmp = node
if tmp.right:
tmp = tmp.right
while tmp:
self.stack.append(tmp)
tmp = tmp.left
return node.val
bst - count left node¶
Write a function count_left_node returns the number of left children in the tree. For example: the following tree has four left children (the nodes storing the values 6, 3, 7, and 10):
9/
6 12
/ /
- 3 8 10 15
/7 18
count_left_node = 4
import unittest
from bst import Node
from bst import bst
def count_left_node(root):
if root is None:
return 0
elif root.left is None:
return count_left_node(root.right)
else:
return 1 + count_left_node(root.left) + count_left_node(root.right)
"""
The tree is created for testing:
9
/ \
6 12
/ \ / \
3 8 10 15
/ \
7 18
count_left_node = 4
"""
class TestSuite(unittest.TestCase):
def setUp(self):
self.tree = bst()
self.tree.insert(9)
self.tree.insert(6)
self.tree.insert(12)
self.tree.insert(3)
self.tree.insert(8)
self.tree.insert(10)
self.tree.insert(15)
self.tree.insert(7)
self.tree.insert(18)
def test_count_left_node(self):
self.assertEqual(4, count_left_node(self.tree.root))
if __name__ == '__main__':
unittest.main()
bst - delete node¶
Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST.
Basically, the deletion can be divided into two stages:
Search for a node to remove. If the node is found, delete the node. Note: Time complexity should be O(height of tree).
Example:
root = [5,3,6,2,4,null,7] key = 3
5/
3 6
/
2 4 7
Given key to delete is 3. So we find the node with value 3 and delete it.
One valid answer is [5,4,6,2,null,null,7], shown in the following BST.
5/
4 6
/
2 7
Another valid answer is [5,2,6,null,4,null,7].
5/
- 2 6
- 4 7
class Solution(object):
def delete_node(self, root, key):
"""
:type root: TreeNode
:type key: int
:rtype: TreeNode
"""
if not root: return None
if root.val == key:
if root.left:
# Find the right most leaf of the left sub-tree
left_right_most = root.left
while left_right_most.right:
left_right_most = left_right_most.right
# Attach right child to the right of that leaf
left_right_most.right = root.right
# Return left child instead of root, a.k.a delete root
return root.left
else:
return root.right
# If left or right child got deleted, the returned root is the child of the deleted node.
elif root.val > key:
root.left = self.deleteNode(root.left, key)
else:
root.right = self.deleteNode(root.right, key)
return root
bst - depth sum¶
Write a function depthSum returns the sum of the values stored in a binary search tree of integers weighted by the depth of each value.
For example:
9/
6 12
/ /
- 3 8 10 15
/7 18
depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18)
import unittest
from bst import Node
from bst import bst
def depth_sum(root, n):
if root:
return recur_depth_sum(root, 1)
def recur_depth_sum(root, n):
if root is None:
return 0
elif root.left is None and root.right is None:
return root.data * n
else:
return n * root.data + recur_depth_sum(root.left, n+1) + recur_depth_sum(root.right, n+1)
"""
The tree is created for testing:
9
/ \
6 12
/ \ / \
3 8 10 15
/ \
7 18
depth_sum = 1*9 + 2*(6+12) + 3*(3+8+10+15) + 4*(7+18)
"""
class TestSuite(unittest.TestCase):
def setUp(self):
self.tree = bst()
self.tree.insert(9)
self.tree.insert(6)
self.tree.insert(12)
self.tree.insert(3)
self.tree.insert(8)
self.tree.insert(10)
self.tree.insert(15)
self.tree.insert(7)
self.tree.insert(18)
def test_depth_sum(self):
self.assertEqual(253, depth_sum(self.tree.root, 4))
if __name__ == '__main__':
unittest.main()
bst - height¶
Write a function height returns the height of a tree. The height is defined to be the number of levels. The empty tree has height 0, a tree of one node has height 1, a root node with one or two leaves as children has height 2, and so on For example: height of tree is 4
9/
6 12
/ /
- 3 8 10 15
/7 18
height = 4
import unittest
from bst import Node
from bst import bst
def height(root):
if root is None:
return 0
else:
return 1 + max(height(root.left), height(root.right))
The tree is created for testing:
9
/ \
6 12
/ \ / \
3 8 10 15
/ \
7 18
count_left_node = 4
class TestSuite(unittest.TestCase):
def setUp(self):
self.tree = bst()
self.tree.insert(9)
self.tree.insert(6)
self.tree.insert(12)
self.tree.insert(3)
self.tree.insert(8)
self.tree.insert(10)
self.tree.insert(15)
self.tree.insert(7)
self.tree.insert(18)
def test_height(self):
self.assertEqual(4, height(self.tree.root))
if __name__ == '__main__':
unittest.main()
bst - is bst¶
Given a binary tree, determine if it is a valid binary search tree (BST).
Assume a BST is defined as follows:
The left subtree of a node contains only nodes with keys less than the node's key. The right subtree of a node contains only nodes with keys greater than the node's key. Both the left and right subtrees must also be binary search trees. Example 1:
2/
1 3
Binary tree [2,1,3], return true. Example 2:
1/
2 3
Binary tree [1,2,3], return false.
def is_bst(root):
"""
:type root: TreeNode
:rtype: bool
"""
if not root:
return True
stack = []
pre = None
while root and stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
if pre and root.val <= pre.val:
return False
pre = root
root = root.right
return True
bst - kth smallest¶
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def kth_smallest(root, k):
stack = []
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
k -= 1
if k == 0:
break
root = root.right
return root.val
class Solution(object):
def kth_smallest(self, root, k):
"""
:type root: TreeNode
:type k: int
:rtype: int
"""
count = []
self.helper(root, count)
return count[k-1]
def helper(self, node, count):
if not node:
return
self.helper(node.left, count)
count.append(node.val)
self.helper(node.right, count)
if __name__ == '__main__':
n1 = Node(100)
n2 = Node(50)
n3 = Node(150)
n4 = Node(25)
n5 = Node(75)
n6 = Node(125)
n7 = Node(175)
n1.left, n1.right = n2, n3
n2.left, n2.right = n4, n5
n3.left, n3.right = n6, n7
print(kth_smallest(n1, 2))
print(Solution().kth_smallest(n1, 2))
bst - lowest common ancestor¶
Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.
- According to the definition of LCA on Wikipedia:
“The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”
_______6______/
___2__ ___8__
/ / 0 _4 7 9
/ 3 5
For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
def lowest_common_ancestor(root, p, q):
"""
:type root: Node
:type p: Node
:type q: Node
:rtype: Node
"""
while root:
if p.val > root.val < q.val:
root = root.right
elif p.val < root.val > q.val:
root = root.left
else:
return root
bst - num empty¶
Write a function num_empty returns returns the number of empty branches in a tree. Function should count the total number of empty branches among the nodes of the tree. A leaf node has two empty branches. In the case, if root is None, it considered as a 1 empty branch For example: the following tree has 10 empty branch (* is empty branch)
9 __/ ___
6 12
/ /
3 8 10 15
/ / / /
- 7 * * * * 18
/ /
empty_branch = 10
import unittest
from bst import Node
from bst import bst
def num_empty(root):
if root is None:
return 1
elif root.left is None and root.right:
return 1 + num_empty(root.right)
elif root.right is None and root.left:
return 1 + num_empty(root.left)
else:
return num_empty(root.left) + num_empty(root.right)
"""
The tree is created for testing:
9
/ \
6 12
/ \ / \
3 8 10 15
/ \
7 18
num_empty = 10
"""
class TestSuite(unittest.TestCase):
def setUp(self):
self.tree = bst()
self.tree.insert(9)
self.tree.insert(6)
self.tree.insert(12)
self.tree.insert(3)
self.tree.insert(8)
self.tree.insert(10)
self.tree.insert(15)
self.tree.insert(7)
self.tree.insert(18)
def test_num_empty(self):
self.assertEqual(10, num_empty(self.tree.root))
if __name__ == '__main__':
unittest.main()
bst - predecessor¶
def predecessor(root, node):
pred = None
while root:
if node.val > root.val:
pred = root
root = root.right
else:
root = root.left
return pred
bst - serialize deserialize¶
class TreeNode(object):
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def serialize(root):
def build_string(node):
if node:
vals.append(str(node.val))
build_string(node.left)
build_string(node.right)
else:
vals.append("#")
vals = []
build_string(root)
return " ".join(vals)
def deserialize(data):
def build_tree():
val = next(vals)
if val == "#":
return None
node = TreeNode(int(val))
node.left = build_tree()
node.right = build_tree()
return node
vals = iter(data.split())
return build_tree()
bst - successor¶
def successor(root, node):
succ = None
while root:
if node.val < root.val:
succ = root
root = root.left
else:
root = root.right
return succ
bst - unique bst¶
Given n, how many structurally unique BST's (binary search trees) that store values 1...n?
For example, Given n = 3, there are a total of 5 unique BST's.
- 1 3 3 2 1
- / / /
- 3 2 1 1 3 2
/ /
2 1 2 3
"""
""" Taking 1~n as root respectively: 1 as root: # of trees = F(0) * F(n-1) // F(0) == 1 2 as root: # of trees = F(1) * F(n-2) 3 as root: # of trees = F(2) * F(n-3) ... n-1 as root: # of trees = F(n-2) * F(1) n as root: # of trees = F(n-1) * F(0)
So, the formulation is: F(n) = F(0) * F(n-1) + F(1) * F(n-2) + F(2) * F(n-3) + ... + F(n-2) * F(1) + F(n-1) * F(0)
def num_trees(n):
"""
:type n: int
:rtype: int
"""
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
for j in range(i+1):
dp[i] += dp[i-j] * dp[j-1]
return dp[-1]
21.3 red black tree¶
Implementation of Red-Black tree.
class RBNode:
def __init__(self, val, is_red, parent=None, left=None, right=None):
self.val = val
self.parent = parent
self.left = left
self.right = right
self.color = is_red
class RBTree:
def __init__(self):
self.root = None
def left_rotate(self, node):
# set the node as the left child node of the current node's right node
right_node = node.right
if right_node is None:
return
else:
# right node's left node become the right node of current node
node.right = right_node.left
if right_node.left is not None:
right_node.left.parent = node
right_node.parent = node.parent
# check the parent case
if node.parent is None:
self.root = right_node
elif node is node.parent.left:
node.parent.left = right_node
else:
node.parent.right = right_node
right_node.left = node
node.parent = right_node
def right_rotate(self, node):
# set the node as the right child node of the current node's left node
left_node = node.left
if left_node is None:
return
else:
# left node's right node become the left node of current node
node.left = left_node.right
if left_node.right is not None:
left_node.right.parent = node
left_node.parent = node.parent
# check the parent case
if node.parent is None:
self.root = left_node
elif node is node.parent.left:
node.parent.left = left_node
else:
node.parent.right = left_node
left_node.right = node
node.parent = left_node
def insert(self, node):
# the inserted node's color is default is red
root = self.root
insert_node_parent = None
# find the position of inserted node
while root is not None:
insert_node_parent = root
if insert_node_parent.val < node.val:
root = root.right
else:
root = root.left
# set the n ode's parent node
node.parent = insert_node_parent
if insert_node_parent is None:
# case 1 inserted tree is null
self.root = node
elif insert_node_parent.val > node.val:
# case 2 not null and find left or right
insert_node_parent.left = node
else:
insert_node_parent.right = node
node.left = None
node.right = None
node.color = 1
# fix the tree to
self.fix_insert(node)
def fix_insert(self, node):
# case 1 the parent is null, then set the inserted node as root and color = 0
if node.parent is None:
node.color = 0
self.root = node
return
# case 2 the parent color is black, do nothing
# case 3 the parent color is red
while node.parent and node.parent.color is 1:
if node.parent is node.parent.parent.left:
uncle_node = node.parent.parent.right
if uncle_node and uncle_node.color is 1:
# case 3.1 the uncle node is red
# then set parent and uncle color is black and grandparent is red
# then node => node.parent
node.parent.color = 0
node.parent.parent.right.color = 0
node.parent.parent.color = 1
node = node.parent.parent
continue
elif node is node.parent.right:
# case 3.2 the uncle node is black or null, and the node is right of parent
# then set his parent node is current node
# left rotate the node and continue the next
node = node.parent
self.left_rotate(node)
# case 3.3 the uncle node is black and parent node is left
# then parent node set black and grandparent set red
node.parent.color = 0
node.parent.parent.color = 1
self.right_rotate(node.parent.parent)
else:
uncle_node = node.parent.parent.left
if uncle_node and uncle_node.color is 1:
# case 3.1 the uncle node is red
# then set parent and uncle color is black and grandparent is red
# then node => node.parent
node.parent.color = 0
node.parent.parent.left.color = 0
node.parent.parent.color = 1
node = node.parent.parent
continue
elif node is node.parent.left:
# case 3.2 the uncle node is black or null, and the node is right of parent
# then set his parent node is current node
# left rotate the node and continue the next
node = node.parent
self.right_rotate(node)
# case 3.3 the uncle node is black and parent node is left
# then parent node set black and grandparent set red
node.parent.color = 0
node.parent.parent.color = 1
self.left_rotate(node.parent.parent)
self.root.color = 0
def transplant(self, node_u, node_v):
"""
replace u with v
:param node_u: replaced node
:param node_v:
:return: None
"""
if node_u.parent is None:
self.root = node_v
elif node_u is node_u.parent.left:
node_u.parent.left = node_v
elif node_u is node_u.parent.right:
node_u.parent.right = node_v
# check is node_v is None
if node_v:
node_v.parent = node_u.parent
def maximum(self, node):
"""
find the max node when node regard as a root node
:param node:
:return: max node
"""
temp_node = node
while temp_node.right is not None:
temp_node = temp_node.right
return temp_node
def minimum(self, node):
"""
find the minimum node when node regard as a root node
:param node:
:return: minimum node
"""
temp_node = node
while temp_node.left:
temp_node = temp_node.left
return temp_node
def delete(self, node):
# find the node position
node_color = node.color
if node.left is None:
temp_node = node.right
self.transplant(node, node.right)
elif node.right is None:
temp_node = node.left
self.transplant(node, node.left)
else:
# both child exits ,and find minimum child of right child
node_min = self.minimum(node.right)
node_color = node_min.color
temp_node = node_min.right
##
if node_min.parent != node:
self.transplant(node_min, node_min.right)
node_min.right = node.right
node_min.right.parent = node_min
self.transplant(node, node_min)
node_min.left = node.left
node_min.left.parent = node_min
node_min.color = node.color
# when node is black, then need to fix it with 4 cases
if node_color == 0:
self.delete_fixup(temp_node)
def delete_fixup(self, node):
# 4 cases
while node != self.root and node.color == 0:
# node is not root and color is black
if node == node.parent.left:
# node is left node
node_brother = node.parent.right
# case 1: node's red, can not get black node
# set brother is black and parent is red
if node_brother.color == 1:
node_brother.color = 0
node.parent.color = 1
self.left_rotate(node.parent)
node_brother = node.parent.right
# case 2: brother node is black, and its children node is both black
if (node_brother.left is None or node_brother.left.color == 0) and (
node_brother.right is None or node_brother.right.color == 0):
node_brother.color = 1
node = node.parent
else:
# case 3: brother node is black , and its left child node is red and right is black
if node_brother.right is None or node_brother.right.color == 0:
node_brother.color = 1
node_brother.left.color = 0
self.right_rotate(node_brother)
node_brother = node.parent.right
# case 4: brother node is black, and right is red, and left is any color
node_brother.color = node.parent.color
node.parent.color = 0
node_brother.right.color = 0
self.left_rotate(node.parent)
node = self.root
break
else:
node_brother = node.parent.left
if node_brother.color == 1:
node_brother.color = 0
node.parent.color = 1
self.left_rotate(node.parent)
node_brother = node.parent.right
if (node_brother.left is None or node_brother.left.color == 0) and (
node_brother.right is None or node_brother.right.color == 0):
node_brother.color = 1
node = node.parent
else:
if node_brother.left is None or node_brother.left.color == 0:
node_brother.color = 1
node_brother.right.color = 0
self.left_rotate(node_brother)
node_brother = node.parent.left
node_brother.color = node.parent.color
node.parent.color = 0
node_brother.left.color = 0
self.right_rotate(node.parent)
node = self.root
break
node.color = 0
def inorder(self):
res = []
if not self.root:
return res
stack = []
root = self.root
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
res.append({'val': root.val, 'color': root.color})
root = root.right
return res
if __name__ == "__main__":
rb = RBTree()
children = [11, 2, 14, 1, 7, 15, 5, 8, 4]
for child in children:
node = RBNode(child, 1)
print(child)
rb.insert(node)
print(rb.inorder())
21.4 segment tree¶
Segment_tree creates a segment tree with a given array and function, allowing queries to be done later in log(N) time function takes 2 values and returns a same type value
class SegmentTree:
def __init__(self,arr,function):
self.segment = [0 for x in range(3*len(arr)+3)]
self.arr = arr
self.fn = function
self.maketree(0,0,len(arr)-1)
def make_tree(self,i,l,r):
if l==r:
self.segment[i] = self.arr[l]
elif l<r:
self.make_tree(2*i+1,l,int((l+r)/2))
self.make_tree(2*i+2,int((l+r)/2)+1,r)
self.segment[i] = self.fn(self.segment[2*i+1],self.segment[2*i+2])
def __query(self,i,L,R,l,r):
if l>R or r<L or L>R or l>r:
return None
if L>=l and R<=r:
return self.segment[i]
val1 = self.__query(2*i+1,L,int((L+R)/2),l,r)
val2 = self.__query(2*i+2,int((L+R+2)/2),R,l,r)
print(L,R," returned ",val1,val2)
if val1 != None:
if val2 != None:
return self.fn(val1,val2)
return val1
return val2
def query(self,L,R):
return self.__query(0,0,len(self.arr)-1,L,R)
'''
Example -
mytree = SegmentTree([2,4,5,3,4],max)
mytree.query(2,4)
mytree.query(0,3) ...
mytree = SegmentTree([4,5,2,3,4,43,3],sum)
mytree.query(1,8)
21.5 traversal¶
traversal - inorder¶
Time complexity : O(n)
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def inorder(root):
res = []
if not root:
return res
stack = []
while root or stack:
while root:
stack.append(root)
root = root.left
root = stack.pop()
res.append(root.val)
root = root.right
return res
# Recursive Implementation
def inorder_rec(root, res=None):
if root is None:
return []
if res is None:
res = []
inorder_rec(root.left, res)
res.append(root.val)
inorder_rec(root.right, res)
return res
if __name__ == '__main__':
n1 = Node(100)
n2 = Node(50)
n3 = Node(150)
n4 = Node(25)
n5 = Node(75)
n6 = Node(125)
n7 = Node(175)
n1.left, n1.right = n2, n3
n2.left, n2.right = n4, n5
n3.left, n3.right = n6, n7
assert inorder(n1) == [25, 50, 75, 100, 125, 150, 175]
assert inorder_rec(n1) == [25, 50, 75, 100, 125, 150, 175]
traversal - level order¶
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).
For example: Given binary tree [3,9,20,null,null,15,7],
3/
- 9 20
/15 7
return its level order traversal as: [
[3], [9,20], [15,7]
]
def level_order(root):
ans = []
if not root:
return ans
level = [root]
while level:
current = []
new_level = []
for node in level:
current.append(node.val)
if node.left:
new_level.append(node.left)
if node.right:
new_level.append(node.right)
level = new_level
ans.append(current)
return ans
traversal - postorder¶
Time complexity : O(n)
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def postorder(root):
res_temp = []
res = []
if not root:
return res
stack = []
stack.append(root)
while stack:
root = stack.pop()
res_temp.append(root.val)
if root.left:
stack.append(root.left)
if root.right:
stack.append(root.right)
while res_temp:
res.append(res_temp.pop())
return res
# Recursive Implementation
def postorder_rec(root, res=None):
if root is None:
return []
if res is None:
res = []
postorder_rec(root.left, res)
postorder_rec(root.right, res)
res.append(root.val)
return res
traversal - preorder¶
Time complexity : O(n)
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
def preorder(root):
res = []
if not root:
return res
stack = []
stack.append(root)
while stack:
root = stack.pop()
res.append(root.val)
if root.right:
stack.append(root.right)
if root.left:
stack.append(root.left)
return res
# Recursive Implementation
def preorder_rec(root, res=None):
if root is None:
return []
if res is None:
res = []
res.append(root.val)
preorder_rec(root.left, res)
preorder_rec(root.right, res)
return res
traversal - zigzag¶
Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between).
For example: Given binary tree [3,9,20,null,null,15,7],
3/
- 9 20
/15 7
return its zigzag level order traversal as: [
[3], [20,9], [15,7]
]
def zigzag_level(root):
res = []
if not root:
return res
level = [root]
flag = 1
while level:
current = []
new_level = []
for node in level:
current.append(node.val)
if node.left:
new_level.append(node.left)
if node.right:
new_level.append(node.right)
level = new_level
res.append(current[::flag])
flag *= -1
return res
21.6 trie¶
trie - add and search¶
We are asked to design an efficient data structure that allows us to add and search for words. The search can be a literal word or regular expression containing “.”, where “.” can be any letter.
Example: addWord(“bad”) addWord(“dad”) addWord(“mad”) search(“pad”) -> false search(“bad”) -> true search(“.ad”) -> true search(“b..”) -> true
import collections
class TrieNode(object):
def __init__(self, letter, is_terminal=False):
self.children = dict()
self.letter = letter
self.is_terminal = is_terminal
class WordDictionary(object):
def __init__(self):
self.root = TrieNode("")
def add_word(self, word):
cur = self.root
for letter in word:
if letter not in cur.children:
cur.children[letter] = TrieNode(letter)
cur = cur.children[letter]
cur.is_terminal = True
def search(self, word, node=None):
cur = node
if not cur:
cur = self.root
for i, letter in enumerate(word):
# if dot
if letter == ".":
if i == len(word) - 1: # if last character
for child in cur.children.itervalues():
if child.is_terminal:
return True
return False
for child in cur.children.itervalues():
if self.search(word[i+1:], child) == True:
return True
return False
# if letter
if letter not in cur.children:
return False
cur = cur.children[letter]
return cur.is_terminal
class WordDictionary2(object):
def __init__(self):
self.word_dict = collections.defaultdict(list)
def add_word(self, word):
if word:
self.word_dict[len(word)].append(word)
def search(self, word):
if not word:
return False
if '.' not in word:
return word in self.word_dict[len(word)]
for v in self.word_dict[len(word)]:
# match xx.xx.x with yyyyyyy
for i, ch in enumerate(word):
if ch != v[i] and ch != '.':
break
else:
return True
return False
trie - trie¶
Implement a trie with insert, search, and startsWith methods.
Note: You may assume that all inputs are consist of lowercase letters a-z.
import collections
class TrieNode:
def __init__(self):
self.children = collections.defaultdict(TrieNode)
self.is_word = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
current = self.root
for letter in word:
current = current.children[letter]
current.is_word = True
def search(self, word):
current = self.root
for letter in word:
current = current.children.get(letter)
if current is None:
return False
return current.is_word
def starts_with(self, prefix):
current = self.root
for letter in prefix:
current = current.children.get(letter)
if current is None:
return False
return True
21.7 bin tree to list¶
class Node():
def __init__(self, val = 0):
self.val = val
self.left = None
self.right = None
def bin_tree_to_list(root):
"""
type root: root class
"""
if not root:
return root
root = bin_tree_to_list_util(root)
while root.left:
root = root.left
return root
def bin_tree_to_list_util(root):
if not root:
return root
if root.left:
left = bin_tree_to_list_util(root.left)
while left.right:
left = left.right
left.right = root
root.left = left
if root.right:
right = bin_tree_to_list_util(root.right)
while right.left:
right = right.left
right.left = root
root.right = right
return root
def print_tree(root):
while root:
print(root.val)
root = root.right
tree = Node(10)
tree.left = Node(12)
tree.right = Node(15)
tree.left.left = Node(25)
tree.left.left.right = Node(100)
tree.left.right = Node(30)
tree.right.left = Node(36)
head = bin_tree_to_list(tree)
print_tree(head)
21.8 binary tree paths¶
def binary_tree_paths(root):
res = []
if not root:
return res
dfs(res, root, str(root.val))
return res
def dfs(res, root, cur):
if not root.left and not root.right:
res.append(cur)
if root.left:
dfs(res, root.left, cur+'->'+str(root.left.val))
if root.right:
dfs(res, root.right, cur+'->'+str(root.right.val))
21.9 deepest left¶
# Given a binary tree, find the deepest node # that is the left child of its parent node.
# Example:
# 1# /
# 2 3
# /
- # 4 5 6
- #
- # 7
# should return 4.
class Node:
def __init__(self, val = None):
self.left = None
self.right = None
self.val = val
class DeepestLeft:
def __init__(self):
self.depth = 0
self.Node = None
def find_deepest_left(root, is_left, depth, res):
if not root:
return
if is_left and depth > res.depth:
res.depth = depth
res.Node = root
find_deepest_left(root.left, True, depth + 1, res)
find_deepest_left(root.right, False, depth + 1, res)
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
root.right.right = Node(6)
root.right.right.right = Node(7)
res = DeepestLeft()
find_deepest_left(root, True, 1, res)
if res.Node:
print(res.Node.val)
21.10 invert tree¶
# invert a binary tree
def reverse(root):
if not root:
return
root.left, root.right = root.right, root.left
if root.left:
reverse(root.left)
if root.right:
reverse(root.right)
21.11 is balanced¶
def is_balanced(root):
"""
O(N) solution
"""
return -1 != get_depth(root)
def get_depth(root):
"""
return 0 if unbalanced else depth + 1
"""
if not root:
return 0
left = get_depth(root.left)
right = get_depth(root.right)
if abs(left-right) > 1 or left == -1 or right == -1:
return -1
return 1 + max(left, right)
################################
def is_balanced(root):
"""
O(N^2) solution
"""
left = max_height(root.left)
right = max_height(root.right)
return abs(left-right) <= 1 and is_balanced(root.left) and is_balanced(root.right)
def max_height(root):
if not root:
return 0
return max(max_height(root.left), max_height(root.right)) + 1
21.12 is subtree¶
Given two binary trees s and t, check if t is a subtree of s. A subtree of a tree t is a tree consisting of a node in t and all of its descendants in t.
Example 1:
Given s:
3/
4 5
/
1 2
Given t:
4/
1 2
Return true, because t is a subtree of s.
Example 2:
Given s:
3/
4 5
/
- 1 2
/0
Given t:
3/
4
/
1 2
Return false, because even though t is part of s, it does not contain all descendants of t.
Follow up: What if one tree is significantly lager than the other?
import collections
def is_subtree(big, small):
flag = False
queue = collections.deque()
queue.append(big)
while queue:
node = queue.popleft()
if node.val == small.val:
flag = comp(node, small)
break
else:
queue.append(node.left)
queue.append(node.right)
return flag
def comp(p, q):
if not p and not q:
return True
if p and q:
return p.val == q.val and comp(p.left,q.left) and comp(p.right, q.right)
return False
21.13 is symmetric¶
Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).
For example, this binary tree [1,2,2,3,4,4,3] is symmetric:
1/
2 2
/ /
3 4 4 3 But the following [1,2,2,null,3,null,3] is not:
1/
- 2 2
- 3 3
Note: Bonus points if you could solve it both recursively and iteratively.
# TC: O(b) SC: O(log n)
def is_symmetric(root):
if not root:
return True
return helper(root.left, root.right)
def helper(p, q):
if not p and not q:
return True
if not p or not q or q.val != p.val:
return False
return helper(p.left, q.right) and helper(p.right, q.left)
def is_symmetric_iterative(root):
if not root:
return True
stack = [[root.left, root.right]]
while stack:
left, right = stack.pop() # popleft
if not left and not right:
continue
if not left or not right:
return False
if left.val == right.val:
stack.append([left.left, right.right])
stack.append([left.right, right.left])
else:
return False
return True
21.14 logest consecutive¶
Given a binary tree, find the length of the longest consecutive sequence path.
The path refers to any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The longest consecutive path need to be from parent to child (cannot be the reverse).
- For example,
- 1
- 3
/
- 2 4
- 5
- Longest consecutive sequence path is 3-4-5, so return 3.
- 2
- 3
/
2
/
1
def longest_consecutive(root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
max_len = 0
dfs(root, 0, root.val, max_len)
return max_len
def dfs(root, cur, target, max_len):
if not root:
return
if root.val == target:
cur += 1
else:
cur = 1
max_len = max(cur, max_len)
dfs(root.left, cur, root.val+1, max_len)
dfs(root.right, cur, root.val+1, max_len)
21.15 lowest common ancestor¶
Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.
- According to the definition of LCA on Wikipedia:
“The lowest common ancestor is defined between two nodes v and w as the lowest node in T that has both v and w as descendants (where we allow a node to be a descendant of itself).”
_______3______/
___5__ ___1__
/ / 6 _2 0 8
/ 7 4
For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
def lca(root, p, q):
"""
:type root: TreeNode
:type p: TreeNode
:type q: TreeNode
:rtype: TreeNode
"""
if not root or root is p or root is q:
return root
left = lca(root.left, p, q)
right = lca(root.right, p, q)
if left and right:
return root
return left if left else right
21.16 max height¶
Given a binary tree, find its maximum depth.
The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
class Node():
def __init__(self, val = 0):
self.val = val
self.left = None
self.right = None
# def max_height(root):
# if not root:
# return 0
# return max(maxDepth(root.left), maxDepth(root.right)) + 1
# iterative
def max_height(root):
if not root:
return 0
height = 0
queue = [root]
while queue:
height += 1
level = []
while queue:
node = queue.pop(0)
if node.left:
level.append(node.left)
if node.right:
level.append(node.right)
queue = level
return height
def print_tree(root):
if root:
print(root.val)
print_tree(root.left)
print_tree(root.right)
tree = Node(10)
tree.left = Node(12)
tree.right = Node(15)
tree.left.left = Node(25)
tree.left.left.right = Node(100)
tree.left.right = Node(30)
tree.right.left = Node(36)
height = max_height(tree)
print_tree(tree)
print("height:", height)
21.17 max path sum¶
def max_path_sum(root):
maximum = float("-inf")
helper(root, maximum)
return maximum
def helper(root, maximum):
if not root:
return 0
left = helper(root.left, maximum)
right = helper(root.right, maximum)
maximum = max(maximum, left+right+root.val)
return root.val + maximum
21.18 min height¶
class Node():
def __init__(self, val = 0):
self.val = val
self.left = None
self.right = None
def min_depth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
if not root.left or not root.right:
return max(self.minDepth(root.left), self.minDepth(root.right))+1
return min(self.minDepth(root.left), self.minDepth(root.right)) + 1
# iterative
def min_height(root):
if not root:
return 0
height = 0
level = [root]
while level:
height += 1
new_level = []
for node in level:
if not node.left and not node.right:
return height
if node.left:
new_level.append(node.left)
if node.right:
new_level.append(node.right)
level = new_level
return height
def print_tree(root):
if root:
print(root.val)
print_tree(root.left)
print_tree(root.right)
tree = Node(10)
tree.left = Node(12)
tree.right = Node(15)
tree.left.left = Node(25)
tree.left.left.right = Node(100)
tree.left.right = Node(30)
tree.right.left = Node(36)
height = min_height(tree)
print_tree(tree)
print("height:", height)
21.19 path sum¶
Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum.
For example: Given the below binary tree and sum = 22,
5/
4 8
/ /
11 13 4
/
7 2 1
return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
def has_path_sum(root, sum):
"""
:type root: TreeNode
:type sum: int
:rtype: bool
"""
if not root:
return False
if not root.left and not root.right and root.val == sum:
return True
sum -= root.val
return has_path_sum(root.left, sum) or has_path_sum(root.right, sum)
21.20 path sum2¶
Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum.
For example: Given the below binary tree and sum = 22,
5/
4 8
/ /
11 13 4
/ /
7 2 5 1
return [
[5,4,11,2], [5,8,4,5]
]
def path_sum(root, sum):
if not root:
return []
res = []
dfs(root, sum, [], res)
return res
def dfs(root, sum, ls, res):
if not root.left and not root.right and root.val == sum:
ls.append(root.val)
res.append(ls)
if root.left:
dfs(root.left, sum-root.val, ls+[root.val], res)
if root.right:
dfs(root.right, sum-root.val, ls+[root.val], res)
# DFS with stack
def path_sum2(root, s):
if not root:
return []
res = []
stack = [(root, [root.val])]
while stack:
node, ls = stack.pop()
if not node.left and not node.right and sum(ls) == s:
res.append(ls)
if node.left:
stack.append((node.left, ls+[node.left.val]))
if node.right:
stack.append((node.right, ls+[node.right.val]))
return res
# BFS with queue
def path_sum3(root, sum):
if not root:
return []
res = []
queue = [(root, root.val, [root.val])]
while queue:
node, val, ls = queue.pop(0) # popleft
if not node.left and not node.right and val == sum:
res.append(ls)
if node.left:
queue.append((node.left, val+node.left.val, ls+[node.left.val]))
if node.right:
queue.append((node.right, val+node.right.val, ls+[node.right.val]))
return res
21.21 pretty print¶
# a -> Adam -> Book -> 4 # b -> Bill -> Computer -> 5 # -> TV -> 6 # Jill -> Sports -> 1 # c -> Bill -> Sports -> 3 # d -> Adam -> Computer -> 3 # Quin -> Computer -> 3 # e -> Quin -> Book -> 5 # -> TV -> 2 # f -> Adam -> Computer -> 7
from __future__ import print_function
def tree_print(tree):
for key in tree:
print(key, end=' ') # end=' ' prevents a newline character
tree_element = tree[key] # multiple lookups is expensive, even amortized O(1)!
for subElem in tree_element:
print(" -> ", subElem, end=' ')
if type(subElem) != str: # OP wants indenting after digits
print("\n ") # newline and a space to match indenting
print() # forces a newline
21.22 same tree¶
Given two binary trees, write a function to check if they are equal or not.
Two binary trees are considered equal if they are structurally identical and the nodes have the same value.
def is_same_tree(p, q):
if not p and not q:
return True
if p and q and p.val == q.val:
return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)
return False
# Time Complexity O(min(N,M))
# where N and M are the number of nodes for the trees.
# Space Complexity O(min(height1, height2))
# levels of recursion is the mininum height between the two trees.
21.23 tree¶
class TreeNode:
def __init__(self, val=0):
self.val = val
self.left = None
self.right = None
chapter 22: Union-find¶
22.1 count islands¶
A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand operation which turns the water at position (row, col) into a land. Given a list of positions to operate, count the number of islands after each addLand operation. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Given m = 3, n = 3, positions = [[0,0], [0,1], [1,2], [2,1]]. Initially, the 2d grid grid is filled with water. (Assume 0 represents water and 1 represents land).
0 0 0 0 0 0 0 0 0 Operation #1: addLand(0, 0) turns the water at grid[0][0] into a land.
1 0 0 0 0 0 Number of islands = 1 0 0 0 Operation #2: addLand(0, 1) turns the water at grid[0][1] into a land.
1 1 0 0 0 0 Number of islands = 1 0 0 0 Operation #3: addLand(1, 2) turns the water at grid[1][2] into a land.
1 1 0 0 0 1 Number of islands = 2 0 0 0 Operation #4: addLand(2, 1) turns the water at grid[2][1] into a land.
1 1 0 0 0 1 Number of islands = 3 0 1 0
class Solution(object):
def num_islands2(self, m, n, positions):
ans = []
islands = Union()
for p in map(tuple, positions):
islands.add(p)
for dp in (0, 1), (0, -1), (1, 0), (-1, 0):
q = (p[0] + dp[0], p[1] + dp[1])
if q in islands.id:
islands.unite(p, q)
ans += [islands.count]
return ans
class Union(object):
def __init__(self):
self.id = {}
self.sz = {}
self.count = 0
def add(self, p):
self.id[p] = p
self.sz[p] = 1
self.count += 1
def root(self, i):
while i != self.id[i]:
self.id[i] = self.id[self.id[i]]
i = self.id[i]
return i
def unite(self, p, q):
i, j = self.root(p), self.root(q)
if i == j:
return
if self.sz[i] > self.sz[j]:
i, j = j, i
self.id[i] = j
self.sz[j] += self.sz[i]
self.count -= 1
chapter 23: Unix¶
23.1 path¶
full path¶
Get a full absolute path a file
import os
def full_path(file):
return os.path.abspath(os.path.expanduser(file))
join with slash¶
Both URL and file path joins use slashes as dividers between their parts. For example:
path/to/dir + file --> path/to/dir/file path/to/dir/ + file --> path/to/dir/file http://algorithms.com/ + part --> http://algorithms.com/part http://algorithms.com + part --> http://algorithms/part
import os
def join_with_slash(base, suffix):
# Remove / trailing
base = base.rstrip('/')
# Remove / leading
suffix = suffix.lstrip('/').rstrip()
full_path = "{}/{}".format(base, suffix)
return full_path
simplify path¶
Given an absolute path for a file (Unix-style), simplify it.
For example, path = "/home/", => "/home" path = "/a/./b/../../c/", => "/c"
Corner Cases:
Did you consider the case where path = "/../"? In this case, you should return "/". Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/". In this case, you should ignore redundant slashes and return "/home/foo".
Reference: https://leetcode.com/problems/simplify-path/description/
import os
def simplify_path_v1(path):
return os.path.abspath(path)
def simplify_path_v2(path):
stack, tokens = [], path.split("/")
for token in tokens:
if token == ".." and stack:
stack.pop()
elif token != ".." and token != "." and token:
stack.append(token)
return "/" + "/".join(stack)
split¶
Splitting a path into 2 parts Example: Input: https://algorithms/unix/test.py (for url) Output:
part[0]: https://algorithms/unix part[1]: test.py
Input: algorithms/unix/test.py (for file path) Output:
part[0]: algorithms/unix part[1]: test.py
import os
def split(path):
parts = []
split_part = path.rpartition('/')
# Takt the origin path without the last part
parts.append(split_part[0])
# Take the last element of list
parts.append(split_part[2])
return parts