Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
isaccanedo committed Apr 27, 2024
0 parents commit 5cdfc7c
Show file tree
Hide file tree
Showing 349 changed files with 27,349 additions and 0 deletions.
5,623 changes: 5,623 additions & 0 deletions README.md

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions Solutions/001.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Problem:
Given a list of numbers, return whether any two sums to k. For example, given
[10, 15, 3, 7] and k of 17, return true since 10 + 7 is 17.
Bonus: Can you do this in one pass?
"""

from typing import List


def check_target_sum(arr: List[int], target: int) -> bool:
# using hash list to store the previously seen values to get access to them in O(1)
previous = set()
for elem in arr:
if (target - elem) in previous:
return True
previous.add(elem)
return False


if __name__ == "__main__":
print(check_target_sum([], 17))
print(check_target_sum([10, 15, 3, 7], 17))
print(check_target_sum([10, 15, 3, 4], 17))


"""
SPECS:
TIME COMPLEXITY: O(n)
SPACE COMPLEXITY: O(n)
"""
43 changes: 43 additions & 0 deletions Solutions/002.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Problem:
Given an array of integers, return a new array such that each element at index i of the
new array is the product of all the numbers in the original array except the one at i.
For example, if our input was [1, 2, 3, 4, 5], the expected output would be
[120, 60, 40, 30, 24]. If our input was [3, 2, 1], the expected output would be
[2, 3, 6].
Follow-up: what if you can't use division?
"""

from typing import List


def product_of_arr_except_ith_elem(arr: List[int]) -> int:
length = len(arr)
result = [1 for _ in range(length)]
# multiplying all the elements on the left of the ith element in the 1st pass
# and all the elements on the right of the ith element in the 2nd pass
prod = 1
for i in range(length):
result[i] *= prod
prod *= arr[i]
prod = 1
for i in range(length - 1, -1, -1):
result[i] *= prod
prod *= arr[i]
return result


if __name__ == "__main__":
print(product_of_arr_except_ith_elem([1, 2, 3, 4, 5]))
print(product_of_arr_except_ith_elem([3, 2, 1]))


"""
SPECS:
TIME COMPLEXITY: O(n)
SPACE COMPLEXITY: O(n)
"""
92 changes: 92 additions & 0 deletions Solutions/003.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
Problem:
Write a program to serialize a tree into a string and deserialize a string into a tree.
"""

from DataStructures.Queue import Queue
from DataStructures.Tree import Node, BinaryTree


def serialize_helper(node: Node) -> str:
# helper function to serialize a binary tree (uses prefix traversal)
# data is padded with single quotes (') and comma (,) is used as a delimiter
if node.right is None and node.left is None:
return f"'{node.val}','None','None'"
elif node.left is not None and node.right is None:
return f"'{node.val}',{serialize_helper(node.left)},'None'"
elif node.left is None and node.right is not None:
return f"'{node.val}','None',{serialize_helper(node.right)}"
elif node.left is not None and node.right is not None:
return (
f"'{node.val}',"
+ f"{serialize_helper(node.left)},"
+ f"{serialize_helper(node.right)}"
)


def serialize(tree: BinaryTree) -> str:
return serialize_helper(tree.root)


def deserialize_helper(node: Node, queue: Queue) -> Node:
# helper function to deserialize a string into a Binary Tree
# data is a queue containing the data as a prefix notation can be easily decoded
# using a queue
left = queue.dequeue().strip("'")
if left != "None":
# if the left child exists, its added to the tree
node.left = Node(left)
node.left = deserialize_helper(node.left, queue)

right = queue.dequeue().strip("'")
if right != "None":
# if the right child exists, its added to the tree
node.right = Node(right)
node.right = deserialize_helper(node.right, queue)
return node


def deserialize(string: str) -> BinaryTree:
# the string needs to have the same format as the binary tree serialization
# eg: data is padded with single quotes (') and comma (,) is used as a delimiter
data = string.split(",")
queue = Queue()
for node in data:
queue.enqueue(node)
tree = BinaryTree()
tree.root = Node(queue.dequeue().strip("'"))
deserialize_helper(tree.root, queue)
return tree


if __name__ == "__main__":
tree = BinaryTree()

tree.root = Node("root")

tree.root.left = Node("left")
tree.root.right = Node("right")

tree.root.left.left = Node("left.left")

print(serialize(tree))

generated_tree = deserialize(
"'root','left','left.left','None','None','None','right','None','None'"
)

print(serialize(generated_tree))


"""
SPECS:
SERIALIZE: (n = Number of Nodes)
TIME COMPLEXITY: O(n)
SPACE COMPLEXITY: O(n)
DESERIALIZE: (n = Number of Characters in the String)
TIME COMPLEXITY: O(n)
SPACE COMPLEXITY: O(n)
"""
49 changes: 49 additions & 0 deletions Solutions/004.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
Problem:
Given an array of integers, find the first missing positive integer in linear time and
constant space. In other words, find the lowest positive integer that does not exist in
the array. The array can contain duplicates and negative numbers as well.
For example, the input [3, 4, -1, 1] should give 2. The input [1, 2, 0] should give 3.
You can modify the input array in-place.
"""

from typing import List


def first_missing_positive_integer(arr: List[int]) -> int:
# placing the positive elements (< length) in their proper position
# proper position index = element - 1
# if after the palcement is complete, index of the 1st element not in its proper
# position is the answer
length = len(arr)
for i in range(length):
correctPos = arr[i] - 1
while 1 <= arr[i] <= length and arr[i] != arr[correctPos]:
arr[i], arr[correctPos] = arr[correctPos], arr[i]
correctPos = arr[i] - 1
# finding the first missing positive integer
for i in range(length):
if i + 1 != arr[i]:
return i + 1
return length + 1


if __name__ == "__main__":
print(first_missing_positive_integer([3, 4, 2, 1]))
print(first_missing_positive_integer([3, 4, -1, 1]))
print(first_missing_positive_integer([1, 2, 5]))
print(first_missing_positive_integer([-1, -2]))


"""
SPECS:
TIME COMPLEXITY: O(n)
SPACE COMPLEXITY: O(1)
NOTE: Even though there is a nested loop it is a O(n) algorithm as the cap on the
maximum iterations is 2 * n [Amortised analysis]
"""
56 changes: 56 additions & 0 deletions Solutions/005.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
Problem:
cons(a, b) constructs a pair, and car(pair) and cdr(pair) returns the first and last
element of that pair. For example, car(cons(3, 4)) returns 3, and cdr(cons(3, 4))
returns 4.
Given this implementation of cons:
def cons(a, b):
def pair(f):
return f(a, b)
return pair
"""

from typing import Callable


# given implementation of cons:
def cons(a, b):
def pair(f):
return f(a, b)

return pair


# car implementation
def car(f: Callable) -> int:
z = lambda x, y: x
return f(z)


# cdr implementation
def cdr(f: Callable) -> int:
z = lambda x, y: y
return f(z)


if __name__ == "__main__":
pair = cons(1, 3)

print(car(pair))
print(cdr(pair))


"""
SPECS:
car:
TIME COMPLEXITY: O(1)
SPACE COMPLEXITY: O(1)
cdr:
TIME COMPLEXITY: O(1)
SPACE COMPLEXITY: O(1)
"""
95 changes: 95 additions & 0 deletions Solutions/006.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Problem:
An XOR linked list is a more memory efficient doubly linked list. Instead of each node
holding next and prev fields, it holds a field named both, which is an XOR of the next
node and the previous node. Implement an XOR linked list; it has an add(element) which
adds the element to the end, and a get(index) which returns the node at index.
If using a language that has no pointers (such as Python), you can assume you have
access to get_pointer and dereference_pointer functions that converts between nodes
and memory addresses.
"""


# Solution copied from:
# https://github.com/r1cc4rdo/daily_coding_problem/blob/master/problems/06


"""
An XOR linked list is a more memory efficient doubly linked list.
Instead of each node holding next and prev fields, it holds a field named both, which
is a XOR of the next node and the previous node. Implement a XOR linked list; it has an
add(element) which adds the element to the end, and a get(index) which returns the node
at index.
NOTE: python does not have actual pointers (id() exists but it is not an actual pointer
in all implementations). For this reason, we use a python list to simulate memory.
Indexes are the addresses in memory. This has the unfortunate consequence that the
travel logic needs to reside in the List class rather than the Node one.
"""

from typing import Tuple


class XORLinkedListNode:
def __init__(self, val: int, prev: int, next: int) -> None:
self.val = val
self.both = prev ^ next

def next_node(self, prev_idx: int) -> int:
return self.both ^ prev_idx

def prev_node(self, next_idx: int) -> int:
return self.both ^ next_idx


class XORLinkedList:
def __init__(self) -> None:
self.memory = [XORLinkedListNode(None, -1, -1)]

def head(self) -> Tuple[int, int, XORLinkedListNode]:
# head node index, prev node index, head node
return 0, -1, self.memory[0]

def add(self, val: int) -> None:
current_node_index, previous_node_index, current_node = self.head()
while True:
# walk down the list until the end is found
next_node_index = current_node.next_node(previous_node_index)
if next_node_index == -1:
# the end is reached
break
previous_node_index, current_node_index = (
current_node_index,
next_node_index,
)
current_node = self.memory[next_node_index]
# allocation
new_node_index = len(self.memory)
current_node.both = previous_node_index ^ new_node_index
self.memory.append(XORLinkedListNode(val, current_node_index, -1))

def get(self, index: int) -> int:
current_index, previous_index, current_node = self.head()
for _ in range(index + 1):
previous_index, current_index = (
current_index,
current_node.next_node(previous_index),
)
current_node = self.memory[current_index]
return current_node.val


if __name__ == "__main__":
xor_linked_list = XORLinkedList()

xor_linked_list.add(1)
xor_linked_list.add(2)
xor_linked_list.add(3)
xor_linked_list.add(4)

print(xor_linked_list.get(0))
print(xor_linked_list.get(1))
print(xor_linked_list.get(2))
print(xor_linked_list.get(3))
Loading

0 comments on commit 5cdfc7c

Please sign in to comment.