Python Algorithms

Problem solving with algorithms and Data structures using Python

written by sean base on following git

https://github.com/TheAlgorithms/Python

_images/chapter0_4.png _images/chapter0_5.png

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.

_images/chapter1-1-1.png _images/chapter1-1-2.png _images/chapter1-1-3.png _images/chapter1-1-4.png

1.1 Bubble Sort

alt text

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)

Source: Wikipedia

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

alt text

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)

Source: Wikipedia

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

alt text

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)

Source: Wikipedia

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

alt text

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)

Source: Wikipedia

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

alt text

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)

Source: Wikipedia

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)

Source: Wikipedia

View the algorithm in action

1.6.1 Heap sort Animation
_images/chapter1-6.gif
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

alt text

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
_images/chapter1-8.gif
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

alt text

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

Source: Wikipedia

View the algorithm in action

1.9.1 Shell sort Animation
_images/chapter1-9.gif

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

_images/chapter1-1-9.png
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.

_images/chapter1-1-5.png _images/chapter1-1-6.png

1.10 Bucket Sort

alt text alt text

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)
_images/chapter1-1-7.png _images/chapter1-1-8.png
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.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
  1. Single out one bit R (right most bit in this solution) to use it as a pivot
  2. Divide all numbers into two groups. One group with a bit in the position R One group without a bit in the position R
  3. 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

  1. 0 0 0 -> Dont take 3 , Dont take 2 , Dont take 1 = { }
  2. 0 0 1 -> Dont take 3 , Dont take 2 , take 1 = { 1 }
  3. 0 1 0 -> Dont take 3 , take 2 , Dont take 1 = { 2 }
  4. 0 1 1 -> Dont take 3 , take 2 , take 1 = { 1 , 2 }
  5. 1 0 0 -> take 3 , Dont take 2 , Dont take 1 = { 3 }
  6. 1 0 1 -> take 3 , Dont take 2 , take 1 = { 1 , 3 }
  7. 1 1 0 -> take 3 , take 2 , Dont take 1 = { 2 , 3 }
  8. 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 / 235
Parsed 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:

  1. Pop (from the end) indexes of smaller elements (they'll be useless).
  2. Append the current index.
  3. Pop (from the front) the index i - k, if it's still in the deque (it falls out of the window).
  4. 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 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:

  1. Its length is at least 6.
  2. It contains at least one digit.
  3. It contains at least one lowercase English character.
  4. 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 - 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