Baisc data structures & algorithms in python
04 August 2024
[ data structures , algorithms , leetcode ]


Data Structures

Linked List

class ListNode:
    def __init__(self, val: int=0, next: Optional[ListNode]=None):
        self.val = val
        self.next = next

def nextNode(node : Optional[ListNode]) -> Optional[ListNode]:
    return node.next if node else None

pre, cur, nxt = None, head, nextNode(head)

Stack

class Stack:
    def __init__(self):
        self.stack = []
        
    def push(self, n: str):
        self.stack += [n]
    
    def peek(self) -> str:
        if self.stack:
            n = self.stack[-1]
        else:
            n = ""
        return n
    
    def pop(self) -> str:
        n = self.peek()
        self.stack = self.stack[:-1]
        return n

Note, this implementation uses list, which is a python implementation of a dynamic array. Using a linkedin-list based deque would be (slightly) better for memory (O(2*N) worst-case vs. O(N)) and time (O(1) amortized vs. O(1)).

from collections import deque

stack = deque(some_list)    # initializaiton, also accepts maxlen parameter
val = stack.pop()           # pop val from RIGHT
stack.extend([val])         # append val to RIGHT
stack.append(val)           # equivalent to above

Queue

from collections import deque

queue = deque(some_list)    # initializaiton, also accepts maxlen parameter
val = queue.popleft()       # pop val from LEFT
queue.extend([val])         # append val to RIGHT
queue.append(val)           # equivalent to above

Binary Tree

class TreeNode:
    def __init__(
            self,
            val: int=0,
            left: Optional[TreeNode]=None,
            right: Optional[TreeNode]=None
        ):
        self.val = val
        self.left = left
        self.right = right

Heap

# min-heap
heapq.heappush(arr, val)
val = heapq.heappop(arr)
# max-heap
heapq.heappush(arr, -1 * val)
val = -1 * heapq.heappop(arr)

Prefix Tree/ Trie

class TreeNode:
    def __init__(self, val):
        self.val = val
        self.children = {}

END = "*"
class Trie:
    def __init__(self):
        self.root = TreeNode("")

    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            if char in node.children:
                node = node.children[char]
            else:
                node.children[char] = TreeNode(char)
                node = node.children[char]
        
        if END not in node.children:
            node.children[END] = TreeNode(END)

    def search(self, word: str) -> bool:
        node = self.root
        for char in word:
            if char in node.children:
                node = node.children[char]
            else:
                return False
        
        if END in node.children:
            return True
        else:
            return False
        

    def startsWith(self, prefix: str) -> bool:
        node = self.root
        for char in prefix:
            if char in node.children:
                node = node.children[char]
            else:
                return False
        return True

Algorithms

def binarySearch(number: int, arr: List[int]) -> int:
    """
    Time Complexity:    O(log(N))
    Space Complexity:   O(1)
    """
    n = len(arr) - 1
    left, right = 0, n
    
    while left <= right:
        mid = (left + right)//2
        
        if number < arr[mid]:
            right = mid - 1
        elif arr[mid] < number:
            left = mid + 1
        else:
            return mid
        
    return (left + right)/2.0

Using the number of islands problem as an example.

def numIslands(self, grid: List[List[str]]) -> int:
        """
        Time Complexity:    O(N)
        Space Complexity:   O(N)
        """
        M, N = len(grid), len(grid[0])
        
        visited = set()
        # BFS to explore/visit current island
        def exploreIsland(x, y):
            queue = deque()
            queue.append( (x, y) )
            while queue:                            # until visited all
                # x, y = queue.pop()                # use for DFS
                x, y = queue.popleft()

                STEPS = ([
                    (0, 1),
                    (1, 0),
                    (0, -1),
                    (-1, 0)
                ])
                neighbors = [(y + dy, x + dx) for dy, dx in STEPS]
                for xn, yn in neighbors:
                    if (0 <= xn < M and             # x_ in range
                        0 <= yn < N and             # y_ in range
                        grid[xn][yn] == "1" and     # land coordinate
                        (xn, yn) not in visited):   # not visited

                        queue.append( (xn, yn) )    # make sure to visit
                        
                visited.add( (x, y) )
            
            return
        
        islands = 0
        for x in range(M):
            for y in range(N):
                if (grid[x][y] == "1" and
                    (x, y) not in visited):
                    islands += 1
                    exploreIsland(x, y)
                    
        return islands

Use stack instead of queue (i.e. use x, y = queue.pop() instead of x, y = queue.popleft()) in BFS.

Sorting

Merge Sort

def mergeSort(arr: List[int]) -> List[int]:
    """
    Time Complexity:    O(N*log(N))
    Space Complexity:   O(N)
    """
    n = len(arr)
    if n <= 1:
        return arr
    else:
        left_sorted = mergeSort(arr[:n//2])     # sort left
        right_sorted = mergeSort(arr[n//2:])    # sort right
        all_sorted = left_sorted+right_sorted   # merge
        return all_sorted