Skip to main content

Arrays and Hashing

Dynamic Array

Dynamic array is an array that grows in size as more elements are added to it. It is implemented by creating a new array with double the size of the original array and copying the elements from the original array to the new array.

class DynamicArray:
    def __init__(self):
        self.capacity = 1
        self.size = 0
        self.arr = [0] * self.capacity

    def add(self, element):
        if self.size == self.capacity:
            self.capacity *= 2
            new_arr = [0] * self.capacity
            for i in range(self.size):
                new_arr[i] = self.arr[i]
            self.arr = new_arr
        self.arr[self.size] = element
        self.size += 1

    def get(self, index):
        if index < 0 or index >= self.size:
            return -1
        return self.arr[index]

    def remove(self, index):
        if index < 0 or index >= self.size:
            return
        for i in range(index, self.size - 1):
            self.arr[i] = self.arr[i+1]
        self.size -= 1

Two Pointers

Two pointers is a technique where two pointers are used to solve a problem. The two pointers can be used to solve problems where we need to find a pair of elements that satisfy a condition.

def two_pointers(arr, target):
    left = 0
    right = len(arr) - 1
    while left < right:
        if arr[left] + arr[right] == target:
            return [left, right]
        elif arr[left] + arr[right] < target:
            left += 1
        else:
            right -= 1
    return [-1, -1]

Hash Usage

Hash is a data structure that stores key-value pairs. It is used to store elements in a way that allows for fast retrieval of elements.

hash_map = {}
hash_map[1] = "one"
hash_map[2] = "two"
hash_map[3] = "three"

print(hash_map[1]) # one
print(hash_map[2]) # two
print(hash_map[3]) # three

Hash Implementation

Hash can be implemented using an array of linked lists. The key is hashed to an index in the array and the value is stored in the linked list at that index.

class Hash:
    def __init__(self):
        self.capacity = 10
        self.arr = [None] * self.capacity

    def hash(self, key):
        return key % self.capacity

    def put(self, key, value):
        index = self.hash(key)
        if self.arr[index] is None:
            self.arr[index] = []
        self.arr[index].append((key, value))

    def get(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            return None
        for k, v in self.arr[index]:
            if k == key:
                return v
        return None

    def remove(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            return
        for i, (k, v) in enumerate(self.arr[index]):
            if k == key:
                self.arr[index].pop(i)
                return

Hash Collision

Hash collision is when two keys hash to the same index in the hash table. It can be resolved by using a linked list to store the key-value pairs at that index.

class Hash:
    def __init__(self):
        self.capacity = 10
        self.arr = [None] * self.capacity

    def hash(self, key):
        return key % self.capacity

    def put(self, key, value):
        index = self.hash(key)
        if self.arr[index] is None:
            self.arr[index] = []
        for i, (k, v) in enumerate(self.arr[index]):
            if k == key:
                self.arr[index][i] = (key, value)
                return
        self.arr[index].append((key, value))

    def get(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            return None
        for k, v in self.arr[index]:
            if k == key:
                return v
        return None

    def remove(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            return
        for i, (k, v) in enumerate(self.arr[index]):
            if k == key:
                self.arr[index].pop(i)
                return

Hash Set

Hash set is a set that stores unique elements. It is implemented using a hash table where the key is the element and the value is a dummy value.

class HashSet:
    def __init__(self):
        self.capacity = 10
        self.arr = [None] * self.capacity

    def hash(self, key):
        return key % self.capacity

    def add(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            self.arr[index] = []
        for k in self.arr[index]:
            if k == key:
                return
        self.arr[index].append(key)

    def contains(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            return False
        for k in self.arr[index]:
            if k == key:
                return True
        return False

    def remove(self, key):
        index = self.hash(key)
        if self.arr[index] is None:
            return
        for i, k in enumerate(self.arr[index]):
            if k == key:
                self.arr[index].pop(i)
                return

Prefix Sums

Prefix sums is a the technique of storing the sum of all elements in the array.


arr = [1, 2, 3, 4, 5]
prefix_sum = [0] * len(arr)

prefix_sum[0] = arr[0]
for i in range(1, len(arr)):
    prefix_sum[i] = prefix_sum[i-1] + arr[i]

print(prefix_sum[2]) # 6 = 3 + 3 = prefix_sum[1] + arr[2] = arr[0] + arr[1] + arr[2] = 1 + 2 + 3
print(prefix_sum[4]) # 15 = 10 + 5 = prefix_sum[3] + arr[4] = arr[0] + arr[1] + arr[2] + arr[3] + arr[4] = 1 + 2 + 3 + 4 + 5