diff --git a/epi_judge_python/absent_value_array.py b/epi_judge_python/absent_value_array.py index 462f9c9ad..ec47c7b4f 100644 --- a/epi_judge_python/absent_value_array.py +++ b/epi_judge_python/absent_value_array.py @@ -1,13 +1,36 @@ from typing import Iterator +import itertools from test_framework import generic_test from test_framework.test_failure import TestFailure +# time: O(n) +# space: O(1), size of counter array depends on the number of buckets def find_missing_element(stream: Iterator[int]) -> int: - # TODO - you fill in here. - return 0 + num_bucket = 1 << 16 + counter = [0] * num_bucket + stream, stream_copy = itertools.tee(stream) + for x in stream: + upper_part_x = x >> 16 + counter[upper_part_x] += 1 + + bucket_capacity = 1 << 16 + candidate_bucket = next(i for i, c in enumerate(counter) if c < bucket_capacity) + candidates = [0] * bucket_capacity + + for x in stream_copy: + upper_part_x = x >> 16 + if candidate_bucket == upper_part_x: + lower_part_x = x & ((1 << 16) - 1) + candidates[lower_part_x] = 1 + + for i, v in enumerate(candidates): + if v == 0: + return (candidate_bucket << 16) | i + + raise ValueError def find_missing_element_wrapper(stream): try: diff --git a/epi_judge_python/advance_by_offsets.py b/epi_judge_python/advance_by_offsets.py index 2245d21e5..6b1785f24 100644 --- a/epi_judge_python/advance_by_offsets.py +++ b/epi_judge_python/advance_by_offsets.py @@ -3,8 +3,33 @@ from test_framework import generic_test +# brute force +# time: O(n^2) +# space: O(n) +""" def can_reach_end(A: List[int]) -> bool: - # TODO - you fill in here. + reachable = [True] + [False] * (len(A) - 1) + for i in range(len(A)): + if not reachable[i]: + continue + if A[i] >= len(A) - i - 1: + return True + for j in range(A[i]): + reachable[i + j + 1] = True + return False +""" + +# iterate once and keep track of furthest +# time: O(n) +# space: O(1) +def can_reach_end(A: List[int]) -> bool: + furthest = 0 + i = 0 + while furthest < len(A) - 1: + furthest = max(furthest, i + A[i]) + if furthest == i: + return False + i += 1 return True diff --git a/epi_judge_python/alternating_array.py b/epi_judge_python/alternating_array.py index ba55ad39f..8678ad4f1 100644 --- a/epi_judge_python/alternating_array.py +++ b/epi_judge_python/alternating_array.py @@ -1,4 +1,5 @@ import functools +import random from typing import List from test_framework import generic_test @@ -6,10 +7,77 @@ from test_framework.test_utils import enable_executor_hook +# for each position find min/max remaining number +# time: O(n^2) +# space: O(1) +""" def rearrange(A: List[int]) -> None: - # TODO - you fill in here. + for i in range(len(A)): + next = i + if i % 2 == 1: + for j in range(i, len(A)): + next = j if A[j] > A[next] else next + else: + for j in range(i, len(A)): + next = j if A[j] < A[next] else next + A[i], A[next] = A[next], A[i] return +""" +# sort and swap elements +# time: O(n log n) +# space: O(1) +""" +def rearrange(A: List[int]) -> None: + A.sort() + for i in range(1, len(A) - 1, 2): + A[i], A[i+1] = A[i+1], A[i] + return +""" + +# find median +# time: O(n) on average, O(n^2) worst case (like quick sort) +# space: O(1) +""" +def rearrange(A: List[int]) -> None: + left = 0 + right = len(A) - 1 + median_pos = (len(A) - 1) // 2 + median = None + count = 0 + while median == None and left < right: + count += 1 + pivot_idx = random.randint(left, right) + pivot = A[pivot_idx] + new_pivot_idx = left + A[right], A[pivot_idx] = A[pivot_idx], A[right] + for i in range(left, right): + if A[i] <= pivot: + A[i], A[new_pivot_idx] = A[new_pivot_idx], A[i] + new_pivot_idx += 1 + A[right], A[new_pivot_idx] = A[new_pivot_idx], A[right] + if new_pivot_idx == median_pos: + median = A[median_pos] + elif new_pivot_idx > median_pos: + right = new_pivot_idx - 1 + else: + left = new_pivot_idx + 1 + if median_pos % 2 == 0: + for i in range(1, median_pos, 2): + A[median_pos - i], A[median_pos + i + 1] = A[median_pos + i + 1], A[median_pos - i] + else: + for i in range(0, median_pos, 2): + A[median_pos - i], A[median_pos + i + 1] = A[median_pos + i + 1], A[median_pos - i] + return +""" + +# compare two local elements and swap if necessary +# time: O(n) +# space: O(1) +def rearrange(A: List[int]) -> None: + for i in range(0, len(A) - 1): + if (i % 2 == 0 and A[i] > A[i + 1]) or (i % 2 == 1 and A[i] < A[i + 1]): + A[i], A[i + 1] = A[i + 1], A[i] @enable_executor_hook def rearrange_wrapper(executor, A): diff --git a/epi_judge_python/apply_permutation.py b/epi_judge_python/apply_permutation.py index 6ac2efed9..b72d2d946 100644 --- a/epi_judge_python/apply_permutation.py +++ b/epi_judge_python/apply_permutation.py @@ -3,8 +3,45 @@ from test_framework import generic_test +# time: O(n) +# space: O(n) +""" def apply_permutation(perm: List[int], A: List[int]) -> None: - # TODO - you fill in here. + result = [0] * len(A) + for i in range(len(perm)): + result[perm[i]] = A[i] + A[:] = result + return +""" + +# swap items in both lists one by one +# time: O(n) +# space: O(1), or O(n) if perm cannot be mutated +""" +def apply_permutation(perm: List[int], A: List[int]) -> None: + i = 0 + while i < len(A): + if perm[i] == i: + i += 1 + else: + A[perm[i]], A[i] = A[i], A[perm[i]] + perm[perm[i]], perm[i] = perm[i], perm[perm[i]] + return +""" + +# swap items in A one by one (and restore perm list) +# time: O(n) +# space: O(1) +def apply_permutation(perm, A) -> None: + for i in range(len(A)): + cur = i + while perm[cur] >= 0: + A[i], A[perm[cur]] = A[perm[cur]], A[i] + prev = cur + cur = perm[cur] + perm[prev] = -1 * perm[prev] - 1 + for i in range(len(A)): + perm[i] = -1 * (perm[i] + 1) return diff --git a/epi_judge_python/buy_and_sell_stock.py b/epi_judge_python/buy_and_sell_stock.py index 9f093fd01..a73503712 100644 --- a/epi_judge_python/buy_and_sell_stock.py +++ b/epi_judge_python/buy_and_sell_stock.py @@ -3,10 +3,48 @@ from test_framework import generic_test +# for every day check all the following days +# time: O(n^2) +# space: O(1) +""" def buy_and_sell_stock_once(prices: List[float]) -> float: - # TODO - you fill in here. - return 0.0 + max_profit = 0 + for i in range(len(prices) - 1): + for j in range(i + 1, len(prices)): + profit = prices[j] - prices[i] + if profit > max_profit: + max_profit = profit + return max_profit +""" +# divide and conquer +# time: O(n log n), because T(n) = 2T(n/2) + O(n) +# space: O(1) +""" +def find_max_profit(prices: List[float], i: int, j: int): + if j - i < 2: + return max(0, prices[j] - prices[i]), min(prices[i:j+1]), max(prices[i:j+1]) + mid = (i + j) // 2 + l_max_profit, l_min, l_max = find_max_profit(prices, i, mid) + r_max_profit, r_min, r_max = find_max_profit(prices, mid + 1, j) + max_profit = max(l_max_profit, r_max_profit, r_max - l_min) + return max_profit, min(l_min, r_min), max(l_max, r_max) + +def buy_and_sell_stock_once(prices: List[float]) -> float: + max_profit, _, _ = find_max_profit(prices, 0, len(prices) - 1) + return max_profit +""" + +# compare current price with min price so far +# time: O(n) +# space: O(1) +def buy_and_sell_stock_once(prices: List[float]) -> float: + max_profit = 0 + min_price = float('inf') + for i in range(len(prices)): + max_profit = max(max_profit, prices[i] - min_price) + min_price = min(min_price, prices[i]) + return max_profit if __name__ == '__main__': exit( diff --git a/epi_judge_python/buy_and_sell_stock_twice.py b/epi_judge_python/buy_and_sell_stock_twice.py index 1796c18ee..ae1e6eec6 100644 --- a/epi_judge_python/buy_and_sell_stock_twice.py +++ b/epi_judge_python/buy_and_sell_stock_twice.py @@ -2,12 +2,46 @@ from test_framework import generic_test - +# find max for all possible slices of the list +# time: O(n^2) +# space: O(1) +""" def buy_and_sell_stock_twice(prices: List[float]) -> float: - # TODO - you fill in here. - return 0.0 + max_profit = 0 + for i in range(1, len(prices)): + profit = find_max_profit(prices, 0, i) + find_max_profit(prices, i + 1, len(prices) - 1) + max_profit = max(max_profit, profit) + return max_profit + +# helper (buy and sell once) +def find_max_profit(prices, i, j) -> float: + max_profit = 0 + min_price = float('inf') + for price in prices[i:j+1]: + max_profit = max(max_profit, price - min_price) + min_price = min(min_price, price) + return max_profit +""" +# save max profit for each position +# time: O(n) +# space: O(n) +def buy_and_sell_stock_twice(prices: List[float]) -> float: + profits = [-1.0] * len(prices) + max_profit = 0 + min_price = float('inf') + for i in range(len(prices)): + max_profit = max(max_profit, prices[i] - min_price) + profits[i] = max_profit + min_price = min(min_price, prices[i]) + profits.insert(0, 0) + max_price = float('-inf') + for i in reversed(range(len(prices))): + max_profit = max(max_profit, max_price - prices[i] + profits[i]) + max_price = max(max_price, prices[i]) + return max_profit + if __name__ == '__main__': exit( generic_test.generic_test_main('buy_and_sell_stock_twice.py', diff --git a/epi_judge_python/circular_queue.py b/epi_judge_python/circular_queue.py index ffcbb4fdf..8d9fa748f 100644 --- a/epi_judge_python/circular_queue.py +++ b/epi_judge_python/circular_queue.py @@ -4,45 +4,59 @@ class Queue: def __init__(self, capacity: int) -> None: - # TODO - you fill in here. - return + self.list = [0] * capacity + self.head = self.tail = self.num_elements = 0 + # time: O(1) (amortized) def enqueue(self, x: int) -> None: - # TODO - you fill in here. - return + if self.size() == len(self.list): + self.resize() + self.list[self.tail] = x + self.tail = (self.tail + 1) % len(self.list) + self.num_elements += 1 + # time: O(1) def dequeue(self) -> int: - # TODO - you fill in here. - return 0 + self.num_elements -= 1 + value = self.list[self.head] + self.head = (self.head + 1) % len(self.list) + return value + # time: O(1) def size(self) -> int: - # TODO - you fill in here. - return 0 + return self.num_elements + + def resize(self) -> None: + self.list = self.list[self.head :] + self.list[: self.head] + [0] * self.size() + self.head = 0 + self.tail = self.size() def queue_tester(ops): q = Queue(1) for (op, arg) in ops: - if op == 'Queue': + if op == "Queue": q = Queue(arg) - elif op == 'enqueue': + elif op == "enqueue": q.enqueue(arg) - elif op == 'dequeue': + elif op == "dequeue": result = q.dequeue() if result != arg: - raise TestFailure('Dequeue: expected ' + str(arg) + ', got ' + - str(result)) - elif op == 'size': + raise TestFailure( + "Dequeue: expected " + str(arg) + ", got " + str(result) + ) + elif op == "size": result = q.size() if result != arg: - raise TestFailure('Size: expected ' + str(arg) + ', got ' + - str(result)) + raise TestFailure("Size: expected " + str(arg) + ", got " + str(result)) else: - raise RuntimeError('Unsupported queue operation: ' + op) + raise RuntimeError("Unsupported queue operation: " + op) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('circular_queue.py', - 'circular_queue.tsv', queue_tester)) + generic_test.generic_test_main( + "circular_queue.py", "circular_queue.tsv", queue_tester + ) + ) diff --git a/epi_judge_python/closest_int_same_weight.py b/epi_judge_python/closest_int_same_weight.py index 20823a749..ece6ff3f8 100644 --- a/epi_judge_python/closest_int_same_weight.py +++ b/epi_judge_python/closest_int_same_weight.py @@ -1,9 +1,25 @@ from test_framework import generic_test +# iterate through bits until they differ and swap them +# time complexity: O(n) +""" def closest_int_same_bit_count(x: int) -> int: - # TODO - you fill in here. - return 0 + i = 0 + while (x >> i) & 1 == (x >> i+1) & 1: + i += 1 + x ^= 0b11 << i + return x +""" + +# find least significant differing bits and swap them +# time complexity: O(1) +def closest_int_same_bit_count(x: int) -> int: + i = (x&-x).bit_length() - 1 # least significant on bit index + if i == 0: + i = (~x&-~x).bit_length() - 1 # least significant off bit index + x ^= 0b11 << i-1 + return x if __name__ == '__main__': diff --git a/epi_judge_python/collatz_checker.py b/epi_judge_python/collatz_checker.py index 9e878cebb..8359e2321 100644 --- a/epi_judge_python/collatz_checker.py +++ b/epi_judge_python/collatz_checker.py @@ -1,9 +1,27 @@ +from typing import Set from test_framework import generic_test def test_collatz_conjecture(n: int) -> bool: - # TODO - you fill in here. - return False + confirmed_nums: Set[int] = set([1]) + + for x in range(2, n+1): + path: Set[int] = set() + cur = x + while cur < 2 ** 32: + if cur in path: + return False + path.add(cur) + if cur < x: + if cur in confirmed_nums: + confirmed_nums.add(x) + break + if cur % 2: + cur *= 3 + cur += 1 + else: + cur //= 2 + return True if __name__ == '__main__': diff --git a/epi_judge_python/convert_base.py b/epi_judge_python/convert_base.py index 1dba99e78..f5b134571 100644 --- a/epi_judge_python/convert_base.py +++ b/epi_judge_python/convert_base.py @@ -1,12 +1,27 @@ from test_framework import generic_test +# time: O(n(1 + log_b2(b1))) def convert_base(num_as_string: str, b1: int, b2: int) -> str: - # TODO - you fill in here. - return '' + if num_as_string == "0": + return "0" + numerals = "0123456789ABCDEF" + isNegative = num_as_string[0] == "-" + num = 0 + for n in num_as_string[isNegative:]: + num = num * b1 + numerals.index(n) + result = [] + while num > 0: + result.append(numerals[num % b2]) + num //= b2 + if isNegative: + result.append("-") + return "".join(reversed(result)) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('convert_base.py', 'convert_base.tsv', - convert_base)) + generic_test.generic_test_main( + "convert_base.py", "convert_base.tsv", convert_base + ) + ) diff --git a/epi_judge_python/delete_kth_last_from_list.py b/epi_judge_python/delete_kth_last_from_list.py index acefc51c6..a63aba219 100644 --- a/epi_judge_python/delete_kth_last_from_list.py +++ b/epi_judge_python/delete_kth_last_from_list.py @@ -4,14 +4,27 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) # Assumes L has at least k nodes, deletes the k-th last node in L. def remove_kth_last(L: ListNode, k: int) -> Optional[ListNode]: - # TODO - you fill in here. - return None + dummy_head = ListNode(0, L) + cur = L + for _ in range(k): + cur = cur.next + node_to_delete = dummy_head + while cur: + cur = cur.next + node_to_delete = node_to_delete.next + node_to_delete.next = node_to_delete.next.next + return dummy_head.next -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('delete_kth_last_from_list.py', - 'delete_kth_last_from_list.tsv', - remove_kth_last)) + generic_test.generic_test_main( + "delete_kth_last_from_list.py", + "delete_kth_last_from_list.tsv", + remove_kth_last, + ) + ) diff --git a/epi_judge_python/delete_node_from_list.py b/epi_judge_python/delete_node_from_list.py index 90c4f3a5f..bcdc34552 100644 --- a/epi_judge_python/delete_node_from_list.py +++ b/epi_judge_python/delete_node_from_list.py @@ -5,9 +5,12 @@ from test_framework.test_utils import enable_executor_hook +# time: O(1) +# space: O(1) # Assumes node_to_delete is not tail. def deletion_from_list(node_to_delete: ListNode) -> None: - # TODO - you fill in here. + node_to_delete.data = node_to_delete.next.data + node_to_delete.next = node_to_delete.next.next return @@ -15,10 +18,10 @@ def deletion_from_list(node_to_delete: ListNode) -> None: def deletion_from_list_wrapper(executor, head, node_to_delete_idx): node_to_delete = head if node_to_delete is None: - raise RuntimeError('List is empty') + raise RuntimeError("List is empty") for _ in range(node_to_delete_idx): if node_to_delete.next is None: - raise RuntimeError('Can\'t delete last node') + raise RuntimeError("Can't delete last node") node_to_delete = node_to_delete.next executor.run(functools.partial(deletion_from_list, node_to_delete)) @@ -26,8 +29,11 @@ def deletion_from_list_wrapper(executor, head, node_to_delete_idx): return head -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('delete_node_from_list.py', - 'delete_node_from_list.tsv', - deletion_from_list_wrapper)) + generic_test.generic_test_main( + "delete_node_from_list.py", + "delete_node_from_list.tsv", + deletion_from_list_wrapper, + ) + ) diff --git a/epi_judge_python/directory_path_normalization.py b/epi_judge_python/directory_path_normalization.py index 64a0efb85..d33ed108e 100644 --- a/epi_judge_python/directory_path_normalization.py +++ b/epi_judge_python/directory_path_normalization.py @@ -1,13 +1,26 @@ from test_framework import generic_test +# time: O(n) def shortest_equivalent_path(path: str) -> str: - # TODO - you fill in here. - return '' + pack_names = [] + for i, token in enumerate(path.split("/")): + if (i == 0 and token == "") or token not in ["", "."]: + if token == ".." and pack_names: + if pack_names[-1] == "..": + pack_names.append(token) + else: + pack_names.pop() + else: + pack_names.append(token) + return "/".join(pack_names) or "/" -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('directory_path_normalization.py', - 'directory_path_normalization.tsv', - shortest_equivalent_path)) + generic_test.generic_test_main( + "directory_path_normalization.py", + "directory_path_normalization.tsv", + shortest_equivalent_path, + ) + ) diff --git a/epi_judge_python/do_lists_overlap.py b/epi_judge_python/do_lists_overlap.py index 4a64c0795..1416fadee 100644 --- a/epi_judge_python/do_lists_overlap.py +++ b/epi_judge_python/do_lists_overlap.py @@ -7,8 +7,61 @@ from test_framework.test_utils import enable_executor_hook -def overlapping_lists(l0: ListNode, l1: ListNode) -> Optional[ListNode]: - # TODO - you fill in here. +# time: O(n^2) +# space: O(n) +# def overlapping_lists( +# l0: Optional[ListNode], l1: Optional[ListNode] +# ) -> Optional[ListNode]: +# visited_l0 = [] +# while l0 and l0 not in visited_l0: +# visited_l0.append(l0) +# l0 = l0.next +# visited_l1 = [] +# while l1 and l1 not in visited_l0 and l1 not in visited_l1: +# visited_l1.append(l1) +# l1 = l1.next +# return l1 if l1 in visited_l0 else None + + +# time: O(n) +# space: O(1) +def overlapping_lists( + l0: Optional[ListNode], l1: Optional[ListNode] +) -> Optional[ListNode]: + def length(L): + slow = fast = L + length = 0 + while slow and fast and fast.next: + slow, fast = slow.next, fast.next.next + length += 1 + if slow is fast: + return -1, slow + while slow: + length += 1 + slow = slow.next + return length, None + + l0_length, l0_loop = length(l0) + l1_length, l1_loop = length(l1) + if (l0_loop == None) != (l1_loop == None): + return None + if l0_loop != None: + cur = l0_loop + while True: + cur = cur.next + if cur is l1_loop: + return cur + if cur is l0_loop: + break + else: + if l0_length > l1_length: + l0, l1 = l1, l0 + for _ in range(abs(int(l0_length - l1_length))): + if l1: + l1 = l1.next + while l0 and l1 and l0 != l1: + l0, l1 = l0.next, l1.next + return l0 return None @@ -38,7 +91,7 @@ def overlapping_lists_wrapper(executor, l0, l1, common, cycle0, cycle1): it = l0 for _ in range(cycle0): if not it: - raise RuntimeError('Invalid input data') + raise RuntimeError("Invalid input data") it = it.next last.next = it @@ -49,7 +102,7 @@ def overlapping_lists_wrapper(executor, l0, l1, common, cycle0, cycle1): it = l1 for _ in range(cycle1): if not it: - raise RuntimeError('Invalid input data') + raise RuntimeError("Invalid input data") it = it.next last.next = it @@ -62,11 +115,12 @@ def overlapping_lists_wrapper(executor, l0, l1, common, cycle0, cycle1): result = executor.run(functools.partial(overlapping_lists, l0, l1)) if not (id(result) in common_nodes or (not common_nodes and not result)): - raise TestFailure('Invalid result') + raise TestFailure("Invalid result") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('do_lists_overlap.py', - 'do_lists_overlap.tsv', - overlapping_lists_wrapper)) + generic_test.generic_test_main( + "do_lists_overlap.py", "do_lists_overlap.tsv", overlapping_lists_wrapper + ) + ) diff --git a/epi_judge_python/do_terminated_lists_overlap.py b/epi_judge_python/do_terminated_lists_overlap.py index f11604f6d..f684f0dad 100644 --- a/epi_judge_python/do_terminated_lists_overlap.py +++ b/epi_judge_python/do_terminated_lists_overlap.py @@ -1,4 +1,5 @@ import functools +from typing import Optional from list_node import ListNode from test_framework import generic_test @@ -6,9 +7,56 @@ from test_framework.test_utils import enable_executor_hook -def overlapping_no_cycle_lists(l0: ListNode, l1: ListNode) -> ListNode: - # TODO - you fill in here. - return ListNode() +# time: O(n^2) +# space: O(n) +# def overlapping_no_cycle_lists(l0: ListNode, l1: ListNode) -> Optional[ListNode]: +# visited = [] +# cur = l0 +# while cur: +# visited.append(cur) +# cur = cur.next +# cur = l1 +# while cur: +# if cur in visited: +# return cur +# cur = cur.next +# return None + +# time: O(n^2) +# space: O(1) +# def overlapping_no_cycle_lists(l0: ListNode, l1: ListNode) -> Optional[ListNode]: +# cur = l0 +# while cur: +# cur_inner = l1 +# while cur_inner: +# if cur == cur_inner: +# return cur +# cur_inner = cur_inner.next +# cur = cur.next +# return None + +# time: O(n) +# space: O(1) +def overlapping_no_cycle_lists( + l0: Optional[ListNode], l1: Optional[ListNode] +) -> Optional[ListNode]: + def length(l: Optional[ListNode]) -> int: + length = 0 + while l: + length += 1 + l = l.next + return length + + l0_len = length(l0) + l1_len = length(l1) + if l0_len > l1_len: + l0, l1 = l1, l0 + for _ in range(abs(l1_len - l0_len)): + if l1: # unnecessary, but pyright will complain without this + l1 = l1.next + while l0 and l1 and l0 != l1: + l0, l1 = l0.next, l1.next + return l0 @enable_executor_hook @@ -30,15 +78,17 @@ def overlapping_no_cycle_lists_wrapper(executor, l0, l1, common): else: l1 = common - result = executor.run(functools.partial(overlapping_no_cycle_lists, l0, - l1)) + result = executor.run(functools.partial(overlapping_no_cycle_lists, l0, l1)) if result != common: - raise TestFailure('Invalid result') + raise TestFailure("Invalid result") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('do_terminated_lists_overlap.py', - 'do_terminated_lists_overlap.tsv', - overlapping_no_cycle_lists_wrapper)) + generic_test.generic_test_main( + "do_terminated_lists_overlap.py", + "do_terminated_lists_overlap.tsv", + overlapping_no_cycle_lists_wrapper, + ) + ) diff --git a/epi_judge_python/dutch_national_flag.py b/epi_judge_python/dutch_national_flag.py index 54ae3ecdd..3000e2bd7 100644 --- a/epi_judge_python/dutch_national_flag.py +++ b/epi_judge_python/dutch_national_flag.py @@ -7,10 +7,69 @@ RED, WHITE, BLUE = range(3) +# create separate lists +# time: O(n) +# space: O(n) +""" +def dutch_flag_partition(pivot_index: int, A: List[int]) -> None: + smaller = [] + equal = [] + larger = [] + pivot = A[pivot_index] + + for i in range(len(A)): + if A[i] < pivot: + smaller.append(A[i]) + elif A[i] == pivot: + equal.append(A[i]) + else: + larger.append(A[i]) + + A[:] = smaller + equal + larger +""" +def swap(A, i, j): + A[i], A[j] = A[j], A[i] + +# iterate twice over array +# time: O(n) +# space: O(1) +""" def dutch_flag_partition(pivot_index: int, A: List[int]) -> None: - # TODO - you fill in here. - return + pivot = A[pivot_index] + + smaller = 0 + for i in range(len(A)): + if A[i] < pivot: + swap(A, i, smaller) + smaller += 1 + + larger = len(A) - 1 + for i in reversed(range(len(A))): + if A[i] > pivot: + swap(A, i, larger) + larger -= 1 +""" + +# iterate once over array +# time: O(n) +# space: O(1) +def dutch_flag_partition(pivot_index: int, A: List[int]) -> None: + smaller = 0 + equal = 0 + larger = len(A) + pivot = A[pivot_index] + + while equal < larger: + if A[equal] < pivot: + swap(A, equal, smaller) + smaller += 1 + equal += 1 + elif A[equal] > pivot: + larger -= 1 + swap(A, equal, larger) + else: + equal += 1 @enable_executor_hook diff --git a/epi_judge_python/evaluate_rpn.py b/epi_judge_python/evaluate_rpn.py index f0f59efd3..1b6072e00 100644 --- a/epi_judge_python/evaluate_rpn.py +++ b/epi_judge_python/evaluate_rpn.py @@ -1,12 +1,25 @@ from test_framework import generic_test +# time: O(n) def evaluate(expression: str) -> int: - # TODO - you fill in here. - return 0 + stack = [] + operators = { + "+": lambda b, a: a + b, + "-": lambda b, a: a - b, + "*": lambda b, a: a * b, + "/": lambda b, a: a // b, + } + for token in expression.split(","): + if token in operators: + stack.append(operators[token](stack.pop(), stack.pop())) + else: + stack.append(int(token)) + return stack.pop() -if __name__ == '__main__': + +if __name__ == "__main__": exit( - generic_test.generic_test_main('evaluate_rpn.py', 'evaluate_rpn.tsv', - evaluate)) + generic_test.generic_test_main("evaluate_rpn.py", "evaluate_rpn.tsv", evaluate) + ) diff --git a/epi_judge_python/even_odd_list_merge.py b/epi_judge_python/even_odd_list_merge.py index 210ab360c..ec991442e 100644 --- a/epi_judge_python/even_odd_list_merge.py +++ b/epi_judge_python/even_odd_list_merge.py @@ -4,13 +4,47 @@ from test_framework import generic_test +# repeatedly point prev node to next node +# time: O(n) +# space: O(1) def even_odd_merge(L: ListNode) -> Optional[ListNode]: - # TODO - you fill in here. - return None + odd_dummy_head = ListNode(0) + prev = odd_dummy_head + cur = L + even = True + even_dummy_tail = None + while cur: + prev.next = cur.next + prev = cur + if even: + even_dummy_tail = cur + cur = cur.next + even = not even + if even_dummy_tail: + even_dummy_tail.next = odd_dummy_head.next + return L -if __name__ == '__main__': +# assign to even and odd lists and concatenate them together +# time: O(n) +# space: O(1) +# def even_odd_merge(L: ListNode) -> Optional[ListNode]: +# even_dummy_head, odd_dummy_head = ListNode(0), ListNode(0) +# tails = [even_dummy_head, odd_dummy_head] +# turn = False +# while L: +# tails[turn].next = L +# tails[turn] = L +# L = L.next +# turn = not turn +# tails[0].next = odd_dummy_head.next +# tails[1].next = None +# return even_dummy_head.next + + +if __name__ == "__main__": exit( - generic_test.generic_test_main('even_odd_list_merge.py', - 'even_odd_list_merge.tsv', - even_odd_merge)) + generic_test.generic_test_main( + "even_odd_list_merge.py", "even_odd_list_merge.tsv", even_odd_merge + ) + ) diff --git a/epi_judge_python/int_as_array_increment.py b/epi_judge_python/int_as_array_increment.py index 1e5cd5d21..d5b568527 100644 --- a/epi_judge_python/int_as_array_increment.py +++ b/epi_judge_python/int_as_array_increment.py @@ -2,10 +2,28 @@ from test_framework import generic_test +# brute force: convert to int, increment, convert back to list +# time: O(n) +# space: O(n) +""" +def plus_one(A: List[int]) -> List[int]: + numberInt = int("".join(map(str, A))) + numberInt += 1 + return list(map(int, str(numberInt))) +""" +# directly work with list +# time: O(n) +# space: O(1) def plus_one(A: List[int]) -> List[int]: - # TODO - you fill in here. - return [] + for i in reversed(range(len(A))): + A[i] += 1 + A[i] %= 10 + if A[i] != 0: + break + if i == 0: + A.insert(0, 1) + return A if __name__ == '__main__': diff --git a/epi_judge_python/int_as_array_multiply.py b/epi_judge_python/int_as_array_multiply.py index e1e59e367..60b45bf44 100644 --- a/epi_judge_python/int_as_array_multiply.py +++ b/epi_judge_python/int_as_array_multiply.py @@ -3,9 +3,43 @@ from test_framework import generic_test +# convert to int, multiply, convert back to lists +# time: O(n*m), where n and m are the number of digits +# space: O(n+m) +""" def multiply(num1: List[int], num2: List[int]) -> List[int]: - # TODO - you fill in here. - return [] + int1 = int("".join(map(str, num1))) + int2 = int("".join(map(str, num2))) + sign = -1 if int1 < 0 else 1 + sign *= -1 if int2 < 0 else 1 + result = list(map(int, str(abs(int1) * abs(int2)))) + result[0] *= sign + return result +""" + +# work with lists directly +# time: O(n*m), where n and m are the number of digits +# space: O(n+m) +def multiply(num1: List[int], num2: List[int]) -> List[int]: + sign = -1 if num1[0] * num2[0] < 0 else 1 + num1[0] = abs(num1[0]) + num2[0] = abs(num2[0]) + + result = [0] * (len(num1) + len(num2)) + for i in reversed(range(len(num1))): + for j in reversed(range(len(num2))): + result[i + j + 1] += num1[i] * num2[j] + result[i + j] += result[i + j + 1] // 10 + result[i + j + 1] %= 10 + + i = 0 + while result[i] == 0 and i < len(result) - 1: + i += 1 + result = result[i:] + + result[0] *= sign + return result + if __name__ == '__main__': diff --git a/epi_judge_python/int_as_list_add.py b/epi_judge_python/int_as_list_add.py index d567c709e..80703fc8d 100644 --- a/epi_judge_python/int_as_list_add.py +++ b/epi_judge_python/int_as_list_add.py @@ -4,12 +4,24 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def add_two_numbers(L1: ListNode, L2: ListNode) -> Optional[ListNode]: - # TODO - you fill in here. - return None + dummy_head = cur = ListNode() + carry = 0 + while L1 or L2 or carry: + sum = carry + (L1.data if L1 else 0) + (L2.data if L2 else 0) + cur.next = ListNode(sum % 10) + carry = sum // 10 + cur = cur.next + L1 = L1.next if L1 else None + L2 = L2.next if L2 else None + return dummy_head.next -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('int_as_list_add.py', - 'int_as_list_add.tsv', add_two_numbers)) + generic_test.generic_test_main( + "int_as_list_add.py", "int_as_list_add.tsv", add_two_numbers + ) + ) diff --git a/epi_judge_python/int_square_root.py b/epi_judge_python/int_square_root.py index 69bc17ae5..fa8610aef 100644 --- a/epi_judge_python/int_square_root.py +++ b/epi_judge_python/int_square_root.py @@ -1,10 +1,21 @@ from test_framework import generic_test +# time: O(log k) +# space: O(1) def square_root(k: int) -> int: - # TODO - you fill in here. - return 0 + lower = 0 + upper = k + while lower <= upper: + mid = lower + (upper - lower) // 2 + + if mid * mid <= k: + lower = mid + 1 + else: + upper = mid - 1 + + return lower - 1 if __name__ == '__main__': exit( diff --git a/epi_judge_python/is_anonymous_letter_constructible.py b/epi_judge_python/is_anonymous_letter_constructible.py index 41df0570f..22b8b8301 100644 --- a/epi_judge_python/is_anonymous_letter_constructible.py +++ b/epi_judge_python/is_anonymous_letter_constructible.py @@ -1,10 +1,31 @@ +from typing import Counter from test_framework import generic_test +# pythonic solution +# time: O(m + n) +# space: O(L + M), where L and M are the numbers of distinct characters in the +# letter and magazine, respectively +# def is_letter_constructible_from_magazine(letter_text: str, +# magazine_text: str) -> bool: +# return len(Counter(letter_text) - Counter(magazine_text)) == 0 + + +# time: O(m + n) +# space: O(L), where L is the number of distinct characters in the letter def is_letter_constructible_from_magazine(letter_text: str, magazine_text: str) -> bool: - # TODO - you fill in here. - return True + letter_count = Counter(letter_text) + + for c in magazine_text: + if c in letter_count: + letter_count[c] -= 1 + if letter_count[c] == 0: + del letter_count[c] + if not letter_count: + break + + return not letter_count if __name__ == '__main__': diff --git a/epi_judge_python/is_list_cyclic.py b/epi_judge_python/is_list_cyclic.py index 97c7b9f75..b1f0347c5 100644 --- a/epi_judge_python/is_list_cyclic.py +++ b/epi_judge_python/is_list_cyclic.py @@ -7,8 +7,82 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n^2) +# space: O(n) +# def has_cycle(head: ListNode) -> Optional[ListNode]: +# visited = list() +# cur = head +# while cur: +# if cur in visited: +# return cur +# visited.append(cur) +# cur = cur.next +# return None + +# time: O(n^2) +# space: O(1) +# def has_cycle(head: ListNode) -> Optional[ListNode]: +# cur = head +# i = 0 +# while cur: +# cur_inner = head +# j = 0 +# while j < i and cur_inner: +# if cur.data == cur_inner.data: +# return cur +# cur_inner = cur_inner.next +# j += 1 +# cur = cur.next +# i += 1 +# return None + +# time: O(n) +# space: O(n) +# def has_cycle(head: ListNode) -> Optional[ListNode]: +# visited = set() +# cur = head +# while cur: +# if cur.data in visited: +# return cur +# visited.add(cur.data) +# cur = cur.next +# return None + +# time: O(n) +# space: O(1) +# def has_cycle(head: ListNode) -> Optional[ListNode]: +# slow = fast = head +# while slow and fast and fast.next: +# slow = slow.next +# fast = fast.next.next +# if slow is fast: +# cycle_len = 0 +# start = slow +# while slow: +# cycle_len += 1 +# slow = slow.next +# if slow is start: +# break +# advanced_it = it = head +# for _ in range(cycle_len): +# advanced_it = advanced_it.next if advanced_it else None +# while it and advanced_it and it is not advanced_it: +# it = it.next +# advanced_it = advanced_it.next +# return it +# return None + +# time: O(n) +# space: O(1) def has_cycle(head: ListNode) -> Optional[ListNode]: - # TODO - you fill in here. + fast = slow = head + while slow and fast and fast.next and fast.next.next: + slow, fast = slow.next, fast.next.next + if slow is fast: + slow = head + while slow and slow is not fast: + slow, fast = slow.next, fast.next + return slow return None @@ -17,7 +91,7 @@ def has_cycle_wrapper(executor, head, cycle_idx): cycle_length = 0 if cycle_idx != -1: if head is None: - raise RuntimeError('Can\'t cycle empty list') + raise RuntimeError("Can't cycle empty list") cycle_start = None cursor = head while cursor.next is not None: @@ -29,7 +103,7 @@ def has_cycle_wrapper(executor, head, cycle_idx): if cursor.data == cycle_idx: cycle_start = cursor if cycle_start is None: - raise RuntimeError('Can\'t find a cycle start') + raise RuntimeError("Can't find a cycle start") cursor.next = cycle_start cycle_length += 1 @@ -37,29 +111,30 @@ def has_cycle_wrapper(executor, head, cycle_idx): if cycle_idx == -1: if result is not None: - raise TestFailure('Found a non-existing cycle') + raise TestFailure("Found a non-existing cycle") else: if result is None: - raise TestFailure('Existing cycle was not found') + raise TestFailure("Existing cycle was not found") cursor = result while True: cursor = cursor.next cycle_length -= 1 if cursor is None or cycle_length < 0: raise TestFailure( - 'Returned node does not belong to the cycle or is not the closest node to the head' + "Returned node does not belong to the cycle or is not the closest node to the head" ) if cursor is result: break if cycle_length != 0: raise TestFailure( - 'Returned node does not belong to the cycle or is not the closest node to the head' + "Returned node does not belong to the cycle or is not the closest node to the head" ) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('is_list_cyclic.py', - 'is_list_cyclic.tsv', - has_cycle_wrapper)) + generic_test.generic_test_main( + "is_list_cyclic.py", "is_list_cyclic.tsv", has_cycle_wrapper + ) + ) diff --git a/epi_judge_python/is_list_palindromic.py b/epi_judge_python/is_list_palindromic.py index 6448cc3f6..ae605a075 100644 --- a/epi_judge_python/is_list_palindromic.py +++ b/epi_judge_python/is_list_palindromic.py @@ -2,13 +2,29 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def is_linked_list_a_palindrome(L: ListNode) -> bool: - # TODO - you fill in here. + fast = slow = L + while slow and fast and fast.next: + slow, fast = slow.next, fast.next.next + dummy_head_rev = ListNode(0) + while slow: + dummy_head_rev.next, slow.next, slow = slow, dummy_head_rev.next, slow.next + first = L + second = dummy_head_rev.next + while first and second: + if first.data != second.data: + return False + first, second = first.next, second.next return True -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('is_list_palindromic.py', - 'is_list_palindromic.tsv', - is_linked_list_a_palindrome)) + generic_test.generic_test_main( + "is_list_palindromic.py", + "is_list_palindromic.tsv", + is_linked_list_a_palindrome, + ) + ) diff --git a/epi_judge_python/is_number_palindromic.py b/epi_judge_python/is_number_palindromic.py index 27e904e8a..a278bc065 100644 --- a/epi_judge_python/is_number_palindromic.py +++ b/epi_judge_python/is_number_palindromic.py @@ -1,8 +1,35 @@ from test_framework import generic_test +import math +# traverse half of the string and compare with second half +# time complexity: O(n) +# space complexity: O(n) +""" def is_palindrome_number(x: int) -> bool: - # TODO - you fill in here. + if x < 0: + return False + strRep = str(x) + length = len(strRep) + for i in range(length): + if strRep[i] != strRep[length-1-i]: + return False + return True +""" + +# using numbers only +# time complexity: O(n) +# space complexity: O(1) +def is_palindrome_number(x: int) -> bool: + if x <= 0: + return x == 0 + num_digits = math.floor(math.log10(x)) + 1 + while num_digits > 1: + if x % 10 != x // 10 ** (num_digits-1): + return False + x %= 10 ** (num_digits-1) # remove most significant digit + x //= 10 # remove least significant digit + num_digits -= 2 return True diff --git a/epi_judge_python/is_string_palindromic_punctuation.py b/epi_judge_python/is_string_palindromic_punctuation.py index 5893d1c2e..085da8da5 100644 --- a/epi_judge_python/is_string_palindromic_punctuation.py +++ b/epi_judge_python/is_string_palindromic_punctuation.py @@ -1,13 +1,40 @@ from test_framework import generic_test +# time: O(n) +# space: O(n) +# def is_palindrome(s: str) -> bool: +# alpha_num = [] +# for c in s: +# if c.isalnum(): +# alpha_num.append(c) +# for i in range(len(alpha_num) // 2): +# if alpha_num[i].lower() != alpha_num[~i].lower(): +# return False +# return True + +# time: O(n) +# space: O(1) def is_palindrome(s: str) -> bool: - # TODO - you fill in here. + low = 0 + high = len(s) - 1 + while low < high: + while not s[low].isalnum() and low < high: + low += 1 + while not s[high].isalnum() and low < high: + high -= 1 + if s[low].lower() != s[high].lower(): + return False + low += 1 + high -= 1 return True -if __name__ == '__main__': +if __name__ == "__main__": exit( generic_test.generic_test_main( - 'is_string_palindromic_punctuation.py', - 'is_string_palindromic_punctuation.tsv', is_palindrome)) + "is_string_palindromic_punctuation.py", + "is_string_palindromic_punctuation.tsv", + is_palindrome, + ) + ) diff --git a/epi_judge_python/is_string_permutable_to_palindrome.py b/epi_judge_python/is_string_permutable_to_palindrome.py index 3e7447c47..02a56aa83 100644 --- a/epi_judge_python/is_string_permutable_to_palindrome.py +++ b/epi_judge_python/is_string_permutable_to_palindrome.py @@ -1,9 +1,19 @@ +from typing import Counter from test_framework import generic_test +# time: O(n) +# space: O(c), where c is the number of distinct characters in the string def can_form_palindrome(s: str) -> bool: - # TODO - you fill in here. - return True + counter = Counter(s) + + num_odd = 0 + + for c in counter.values(): + if c % 2: + num_odd += 1 + + return num_odd <= 1 if __name__ == '__main__': diff --git a/epi_judge_python/is_tree_balanced.py b/epi_judge_python/is_tree_balanced.py index 067552fc5..f45904d17 100644 --- a/epi_judge_python/is_tree_balanced.py +++ b/epi_judge_python/is_tree_balanced.py @@ -1,14 +1,28 @@ +from typing import Tuple from binary_tree_node import BinaryTreeNode from test_framework import generic_test +# time: O(n) +# space: O(h), where h is the height of the tree def is_balanced_binary_tree(tree: BinaryTreeNode) -> bool: - # TODO - you fill in here. - return True + def is_balanced(root: BinaryTreeNode | None) -> Tuple[bool, int]: + if not root: + return True, -1 + l_balanced, l_height = is_balanced(root.left) + r_balanced, r_height = is_balanced(root.right) + return ( + l_balanced and r_balanced and abs(l_height - r_height) <= 1, + max(l_height, r_height) + 1, + ) + balanced, _ = is_balanced(tree) + return balanced -if __name__ == '__main__': + +if __name__ == "__main__": exit( - generic_test.generic_test_main('is_tree_balanced.py', - 'is_tree_balanced.tsv', - is_balanced_binary_tree)) + generic_test.generic_test_main( + "is_tree_balanced.py", "is_tree_balanced.tsv", is_balanced_binary_tree + ) + ) diff --git a/epi_judge_python/is_tree_symmetric.py b/epi_judge_python/is_tree_symmetric.py index 0646f6d24..fdd164196 100644 --- a/epi_judge_python/is_tree_symmetric.py +++ b/epi_judge_python/is_tree_symmetric.py @@ -1,13 +1,54 @@ +from typing import Deque from binary_tree_node import BinaryTreeNode from test_framework import generic_test +# iterative +# time: O(n) +# space: O(n) +# def is_symmetric(tree: BinaryTreeNode) -> bool: +# q_left = Deque() +# q_right = Deque() +# q_left.append(tree.left if tree and tree.left else BinaryTreeNode(None)) +# q_right.append(tree.right if tree and tree.right else BinaryTreeNode(None)) + +# while q_left and q_right: +# l = q_left.popleft() +# r = q_right.popleft() +# if l.data != r.data: +# return False +# if l.left or l.right or r.left or r.right: +# q_left.append(l.left if l.left else BinaryTreeNode(None)) +# q_left.append(l.right if l.right else BinaryTreeNode(None)) +# q_right.append(r.right if r.right else BinaryTreeNode(None)) +# q_right.append(r.left if r.left else BinaryTreeNode(None)) + +# return 0 == len(q_left) == len(q_right) + +# recursive +# time: O(n) +# space: O(h), where h is the height of the tree def is_symmetric(tree: BinaryTreeNode) -> bool: - # TODO - you fill in here. - return True + def recursive_check(t1, t2) -> bool: + if not t1 and not t2: + return True + elif t1 and t2: + return ( + t1.data == t2.data + and recursive_check(t1.left, t2.right) + and recursive_check(t1.right, t2.left) + ) + else: + return False + + if not tree: + return True + return recursive_check(tree.left, tree.right) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('is_tree_symmetric.py', - 'is_tree_symmetric.tsv', is_symmetric)) + generic_test.generic_test_main( + "is_tree_symmetric.py", "is_tree_symmetric.tsv", is_symmetric + ) + ) diff --git a/epi_judge_python/is_valid_parenthesization.py b/epi_judge_python/is_valid_parenthesization.py index b9d93d25b..3ec5dd5dc 100644 --- a/epi_judge_python/is_valid_parenthesization.py +++ b/epi_judge_python/is_valid_parenthesization.py @@ -1,13 +1,28 @@ from test_framework import generic_test +# time: O(n) def is_well_formed(s: str) -> bool: - # TODO - you fill in here. - return True + parens = { + ")": "(", + "]": "[", + "}": "{", + } + stack = [] + for p in s: + if p in parens: + if not stack or stack.pop() != parens[p]: + return False + else: + stack.append(p) + return not stack -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('is_valid_parenthesization.py', - 'is_valid_parenthesization.tsv', - is_well_formed)) + generic_test.generic_test_main( + "is_valid_parenthesization.py", + "is_valid_parenthesization.tsv", + is_well_formed, + ) + ) diff --git a/epi_judge_python/is_valid_sudoku.py b/epi_judge_python/is_valid_sudoku.py index d5fc37f22..053294455 100644 --- a/epi_judge_python/is_valid_sudoku.py +++ b/epi_judge_python/is_valid_sudoku.py @@ -3,13 +3,43 @@ from test_framework import generic_test -# Check if a partially filled matrix has any conflicts. +# time: O(n^2) +# space: O(n) def is_valid_sudoku(partial_assignment: List[List[int]]) -> bool: - # TODO - you fill in here. + def is_valid_part(part: List[int]) -> bool: + filled_entries = [0] * (len(part) + 1) + for entry in part: + if entry != 0: + if filled_entries[entry] > 0: + return False + filled_entries[entry] += 1 + return True + + for row in partial_assignment: + if not is_valid_part(row): + return False + + for i in range(len(partial_assignment[0])): + col = [] + for row in partial_assignment: + col.append(row[i]) + if not is_valid_part(col): + return False + + for row in range(0, len(partial_assignment), 3): + for col in range(0, len(partial_assignment[0]), 3): + square = [] + for i in range(3): + for j in range(3): + square.append(partial_assignment[row + i][col + j]) + if not is_valid_part(square): + return False return True -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('is_valid_sudoku.py', - 'is_valid_sudoku.tsv', is_valid_sudoku)) + generic_test.generic_test_main( + "is_valid_sudoku.py", "is_valid_sudoku.tsv", is_valid_sudoku + ) + ) diff --git a/epi_judge_python/k_closest_stars.py b/epi_judge_python/k_closest_stars.py index 3e1ff9e3e..c020d29e6 100644 --- a/epi_judge_python/k_closest_stars.py +++ b/epi_judge_python/k_closest_stars.py @@ -1,6 +1,7 @@ import functools import math -from typing import Iterator, List +from typing import Iterator, List, Tuple +import heapq from test_framework import generic_test from test_framework.test_utils import enable_executor_hook @@ -26,10 +27,36 @@ def __str__(self): def __eq__(self, rhs): return math.isclose(self.distance, rhs.distance) - +# time: O(n log n) +# space: O(n), would not work with this size of dataset +# def find_closest_k_stars(stars: Iterator[Star], k: int) -> List[Star]: +# min_heap = [] +# result = [] +# +# for star in stars: +# heapq.heappush(min_heap, star) +# +# for _ in range(k): +# result.append(heapq.heappop(min_heap)) +# +# return result + + +# time: O(n log k) +# space: O(k) def find_closest_k_stars(stars: Iterator[Star], k: int) -> List[Star]: - # TODO - you fill in here. - return [] + max_heap: List[Tuple[float, Star]] = [] + result = [] + + for star in stars: + heapq.heappush(max_heap, (-star.distance, star)) + if len(max_heap) > k: + heapq.heappop(max_heap) + + while max_heap: + result.append(heapq.heappop(max_heap)[1]) + + return result def comp(expected_output, output): diff --git a/epi_judge_python/k_largest_in_heap.py b/epi_judge_python/k_largest_in_heap.py index f67e7f761..1103b6663 100644 --- a/epi_judge_python/k_largest_in_heap.py +++ b/epi_judge_python/k_largest_in_heap.py @@ -1,11 +1,24 @@ from typing import List +import heapq from test_framework import generic_test, test_utils +# time: O(k log k) +# space: O(k) def k_largest_in_binary_heap(A: List[int], k: int) -> List[int]: - # TODO - you fill in here. - return [] + result = [] + candidates_max_heap = [(-A[0], 0)] + + for _ in range(k): + v, i = heapq.heappop(candidates_max_heap) + result.append(-v) + + for c in [2 * i + 1, 2 * i + 2]: + if c < len(A): + heapq.heappush(candidates_max_heap, (-A[c], c)) + + return result if __name__ == '__main__': diff --git a/epi_judge_python/kth_largest_in_array.py b/epi_judge_python/kth_largest_in_array.py index 864c3c0ef..16af3b930 100644 --- a/epi_judge_python/kth_largest_in_array.py +++ b/epi_judge_python/kth_largest_in_array.py @@ -1,4 +1,5 @@ from typing import List +import random from test_framework import generic_test @@ -6,10 +7,41 @@ # The numbering starts from one, i.e., if A = [3, 1, -1, 2] # find_kth_largest(1, A) returns 3, find_kth_largest(2, A) returns 2, # find_kth_largest(3, A) returns 1, and find_kth_largest(4, A) returns -1. + +# time: O(n), technically, the worst case is O(n^2), if the lowest or highest +# value is randomly picked every time. However, the possibility of +# this happening decreases exponentially with the size of the array. +# space: O(1) def find_kth_largest(k: int, A: List[int]) -> int: - # TODO - you fill in here. - return 0 + def partition(lower, upper, pivot_idx): + pivot_value = A[pivot_idx] + A[pivot_idx], A[upper] = A[upper], A[pivot_idx] + new_pivot_idx = lower + + for i in range(lower, upper): + if A[i] > pivot_value: + A[i], A[new_pivot_idx] = A[new_pivot_idx], A[i] + new_pivot_idx += 1 + + A[new_pivot_idx], A[upper] = A[upper], A[new_pivot_idx] + return new_pivot_idx + + lower = 0 + upper = len(A) - 1 + + while lower <= upper: + pivot_idx = random.randint(lower, upper) + new_pivot_idx = partition(lower, upper, pivot_idx) + + if new_pivot_idx == k - 1: + return A[new_pivot_idx] + elif new_pivot_idx < k - 1: + lower = new_pivot_idx + 1 + else: + upper = new_pivot_idx - 1 + # not possible - just for satisfying the linter + return -1 if __name__ == '__main__': exit( diff --git a/epi_judge_python/kth_node_in_tree.py b/epi_judge_python/kth_node_in_tree.py index 3f1f0dd6a..ff1f88810 100644 --- a/epi_judge_python/kth_node_in_tree.py +++ b/epi_judge_python/kth_node_in_tree.py @@ -14,10 +14,33 @@ def __init__(self, data=None, left=None, right=None, size=None): self.size = size -def find_kth_node_binary_tree(tree: BinaryTreeNode, - k: int) -> Optional[BinaryTreeNode]: - # TODO - you fill in here. - return None +# time: O(h) +# space: O(h), maybe O(1) because of TCO? +# def find_kth_node_binary_tree(tree: BinaryTreeNode, k: int) -> Optional[BinaryTreeNode]: +# def traverse(tree: BinaryTreeNode, traversed_nodes): +# cur_pos = traversed_nodes + (tree.left.size if tree.left else 0) + 1 +# if cur_pos == k: +# return tree +# if cur_pos > k: +# return traverse(tree.left, traversed_nodes) +# return traverse(tree.right, cur_pos) + +# return traverse(tree, 0) + +# time: O(h) +# space: O(1) +def find_kth_node_binary_tree(tree: BinaryTreeNode, k: int) -> Optional[BinaryTreeNode]: + while tree: + cur_pos = (tree.left.size if tree.left else 0) + 1 + if k == cur_pos: + return tree + if k < cur_pos: + tree = tree.left + else: + tree = tree.right + k -= cur_pos + + return tree @enable_executor_hook @@ -30,16 +53,18 @@ def init_size(node): init_size(tree) - result = executor.run(functools.partial(find_kth_node_binary_tree, tree, - k)) + result = executor.run(functools.partial(find_kth_node_binary_tree, tree, k)) if not result: - raise TestFailure('Result can\'t be None') + raise TestFailure("Result can't be None") return result.data -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('kth_node_in_tree.py', - 'kth_node_in_tree.tsv', - find_kth_node_binary_tree_wrapper)) + generic_test.generic_test_main( + "kth_node_in_tree.py", + "kth_node_in_tree.tsv", + find_kth_node_binary_tree_wrapper, + ) + ) diff --git a/epi_judge_python/list_cyclic_right_shift.py b/epi_judge_python/list_cyclic_right_shift.py index b036895d7..555ae9b29 100644 --- a/epi_judge_python/list_cyclic_right_shift.py +++ b/epi_judge_python/list_cyclic_right_shift.py @@ -4,13 +4,31 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def cyclically_right_shift_list(L: ListNode, k: int) -> Optional[ListNode]: - # TODO - you fill in here. - return None + if not L: + return L + length = 1 + tail = L + while tail.next: + length += 1 + tail = tail.next + tail.next = L + k %= length + cur = L + for _ in range(length - k - 1): + cur = cur.next + L = cur.next + cur.next = None + return L -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('list_cyclic_right_shift.py', - 'list_cyclic_right_shift.tsv', - cyclically_right_shift_list)) + generic_test.generic_test_main( + "list_cyclic_right_shift.py", + "list_cyclic_right_shift.tsv", + cyclically_right_shift_list, + ) + ) diff --git a/epi_judge_python/longest_contained_interval.py b/epi_judge_python/longest_contained_interval.py index c75c75610..85fa135a4 100644 --- a/epi_judge_python/longest_contained_interval.py +++ b/epi_judge_python/longest_contained_interval.py @@ -1,12 +1,69 @@ -from typing import List +from typing import FrozenSet, List, Set from test_framework import generic_test +# time: O(n^2) +# space: O(n) +# def longest_contained_range(A: List[int]) -> int: +# result = 0 +# groups: Set[FrozenSet[int]] = set() +# +# for a in A: +# new_group: Set[int] = set([a]) +# adj_groups: Set[FrozenSet[int]] = set() +# for g in groups: +# if a+1 in g or a-1 in g: +# adj_groups.add(g) +# new_group |= g +# for g in adj_groups: +# groups.remove(g) +# groups.add(frozenset(new_group)) +# if len(new_group) > result: +# result = len(new_group) +# +# return result + + +# time: O(n log n) +# space: O(n) +# def longest_contained_range(A: List[int]) -> int: +# longest = 0 +# +# prev_a = None +# current = 0 +# for a in sorted(set(A)): +# if prev_a is None or a == prev_a + 1: +# current += 1 +# else: +# if current > longest: +# longest = current +# current = 1 +# prev_a = a +# +# return max(longest, current) + + +# time: O(n) +# space: O(n) def longest_contained_range(A: List[int]) -> int: - # TODO - you fill in here. - return 0 + result = 0 + remaining_values = set(A) + + while remaining_values: + a = remaining_values.pop() + upper = a + 1 + while upper in remaining_values: + remaining_values.remove(upper) + upper += 1 + lower = a - 1 + while lower in remaining_values: + remaining_values.remove(lower) + lower -= 1 + if upper - lower - 1 > result: + result = upper - lower - 1 + return result if __name__ == '__main__': exit( diff --git a/epi_judge_python/longest_subarray_with_distinct_values.py b/epi_judge_python/longest_subarray_with_distinct_values.py index ec1c836ae..24e0b6fe5 100644 --- a/epi_judge_python/longest_subarray_with_distinct_values.py +++ b/epi_judge_python/longest_subarray_with_distinct_values.py @@ -1,12 +1,54 @@ -from typing import List +from typing import Counter, Dict, List from test_framework import generic_test +# time: O(n^2) +# space: O(n) +# def longest_subarray_with_distinct_entries(A: List[int]) -> int: +# def distinct(counter: Counter[int]) -> bool: +# for _, c in counter.items(): +# if c > 1: +# return False +# return True +# +# max_length = 0 +# left = 0 +# right = 0 +# counter: Counter[int] = Counter(A[0:1]) +# +# while left <= right < len(A): +# if distinct(counter): +# length = right - left + 1 +# if length > max_length: +# max_length = length +# right += 1 +# counter.update(A[right:right+1]) +# else: +# counter[A[left]] -= 1 +# left += 1 +# +# return max_length + + +# time: O(n) +# space: O(n) def longest_subarray_with_distinct_entries(A: List[int]) -> int: - # TODO - you fill in here. - return 0 + most_recent_occurence: Dict[int, int] = dict() + longest_subarray_start = 0 + result = 0 + + for i, a in enumerate(A): + if a in most_recent_occurence: + prev_a_idx = most_recent_occurence[a] + if prev_a_idx >= longest_subarray_start: + length = i - longest_subarray_start + if length > result: + result = length + longest_subarray_start = prev_a_idx + 1 + most_recent_occurence[a] = i + return max(result, len(A) - longest_subarray_start) if __name__ == '__main__': exit( diff --git a/epi_judge_python/look_and_say.py b/epi_judge_python/look_and_say.py index 600e8e21c..6982537bc 100644 --- a/epi_judge_python/look_and_say.py +++ b/epi_judge_python/look_and_say.py @@ -1,12 +1,41 @@ from test_framework import generic_test +import itertools +# nested loops +# time: O(n * 2^n) +# space: O(n * 2^n) +# def look_and_say(n: int) -> str: +# result = [1] +# for _ in range(1, n): +# i = 0 +# current = [] +# while i < len(result): +# num = result[i] +# count = 0 +# while i < len(result) and result[i] == num: +# count += 1 +# i += 1 +# current.append(count) +# current.append(num) +# result = current +# return "".join(map(str, result)) + +# groupby +# time: O(n * 2^n) +# space: O(n * 2^n) def look_and_say(n: int) -> str: - # TODO - you fill in here. - return '' + result = "1" + for _ in range(1, n): + result = "".join( + [str(len(list(gr))) + k for k, gr in itertools.groupby(result)] + ) + return result -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('look_and_say.py', 'look_and_say.tsv', - look_and_say)) + generic_test.generic_test_main( + "look_and_say.py", "look_and_say.tsv", look_and_say + ) + ) diff --git a/epi_judge_python/lowest_common_ancestor.py b/epi_judge_python/lowest_common_ancestor.py index 2d9bad5d2..970899614 100644 --- a/epi_judge_python/lowest_common_ancestor.py +++ b/epi_judge_python/lowest_common_ancestor.py @@ -3,31 +3,59 @@ from binary_tree_node import BinaryTreeNode from test_framework import generic_test -from test_framework.binary_tree_utils import must_find_node, strip_parent_link +from test_framework.binary_tree_utils import ( + collections, + must_find_node, + strip_parent_link, +) from test_framework.test_failure import TestFailure from test_framework.test_utils import enable_executor_hook -def lca(tree: BinaryTreeNode, node0: BinaryTreeNode, - node1: BinaryTreeNode) -> Optional[BinaryTreeNode]: - # TODO - you fill in here. - return None +# time: O(n) +# space: O(h), where h is the height of the tree +def lca( + tree: BinaryTreeNode, node0: BinaryTreeNode, node1: BinaryTreeNode +) -> Optional[BinaryTreeNode]: + Result = collections.namedtuple("Result", ("count", "ancestor")) + + def lca_helper( + tree: BinaryTreeNode | None, node0: BinaryTreeNode, node1: BinaryTreeNode + ) -> Result: + if not tree: + return Result(count=0, ancestor=None) + + left_result = lca_helper(tree.left, node0, node1) + if left_result.count == 2: + return left_result + + right_result = lca_helper(tree.right, node0, node1) + if right_result.count == 2: + return right_result + + cnt = left_result.count + right_result.count + (node0, node1).count(tree) + return Result(cnt, tree if cnt == 2 else None) + + return lca_helper(tree, node0, node1).ancestor @enable_executor_hook def lca_wrapper(executor, tree, key1, key2): strip_parent_link(tree) result = executor.run( - functools.partial(lca, tree, must_find_node(tree, key1), - must_find_node(tree, key2))) + functools.partial( + lca, tree, must_find_node(tree, key1), must_find_node(tree, key2) + ) + ) if result is None: - raise TestFailure('Result can\'t be None') + raise TestFailure("Result can't be None") return result.data -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('lowest_common_ancestor.py', - 'lowest_common_ancestor.tsv', - lca_wrapper)) + generic_test.generic_test_main( + "lowest_common_ancestor.py", "lowest_common_ancestor.tsv", lca_wrapper + ) + ) diff --git a/epi_judge_python/lowest_common_ancestor_close_ancestor.py b/epi_judge_python/lowest_common_ancestor_close_ancestor.py index 594458f7e..69a9faada 100644 --- a/epi_judge_python/lowest_common_ancestor_close_ancestor.py +++ b/epi_judge_python/lowest_common_ancestor_close_ancestor.py @@ -8,9 +8,24 @@ from test_framework.test_utils import enable_executor_hook +# time: O(h) +# space: O(h) def lca(node0: BinaryTreeNode, node1: BinaryTreeNode) -> Optional[BinaryTreeNode]: - # TODO - you fill in here. + visited = set() + + while node0 or node1: + if node0 in visited: + return node0 + if node0: + visited.add(node0) + node0 = node0.parent + if node1 in visited: + return node1 + if node1: + visited.add(node1) + node1 = node1.parent + return None diff --git a/epi_judge_python/lowest_common_ancestor_with_parent.py b/epi_judge_python/lowest_common_ancestor_with_parent.py index dc338e7f6..54bbad6f5 100644 --- a/epi_judge_python/lowest_common_ancestor_with_parent.py +++ b/epi_judge_python/lowest_common_ancestor_with_parent.py @@ -1,3 +1,4 @@ +from collections import deque import functools from typing import Optional @@ -8,25 +9,78 @@ from test_framework.test_utils import enable_executor_hook -def lca(node0: BinaryTreeNode, - node1: BinaryTreeNode) -> Optional[BinaryTreeNode]: - # TODO - you fill in here. - return None +# time: O(h^2) +# space: O(1) +# def lca(node0: BinaryTreeNode, node1: BinaryTreeNode) -> Optional[BinaryTreeNode]: +# it0 = node0 +# it1: BinaryTreeNode +# while it0.parent: +# it1 = node1 +# while it1.parent: +# if it0.data == it1.data: +# return it0 +# it1 = it1.parent +# it0 = it0.parent +# return it0 + +# time: O(h) +# space: O(h) +# def lca(node0: BinaryTreeNode, node1: BinaryTreeNode) -> Optional[BinaryTreeNode]: +# path0 = deque() +# path1 = deque() +# while node0: +# path0.appendleft(node0) +# node0 = node0.parent +# while node1: +# path1.appendleft(node1) +# node1 = node1.parent +# commonAncestor = None +# i = 0 +# minPath = min(len(path0), len(path1)) +# while i < minPath and path0[i] == path1[i]: +# commonAncestor = path0[i] +# i += 1 +# return commonAncestor + +# time: O(h) +# space: O(1) +def lca(node0: BinaryTreeNode, node1: BinaryTreeNode) -> Optional[BinaryTreeNode]: + def get_depth(node) -> int: + depth = 0 + while node.parent: + depth += 1 + node = node.parent + return depth + + d0, d1 = get_depth(node0), get_depth(node1) + + for _ in range(d0 - d1): + node0 = node0.parent + for _ in range(d1 - d0): + node1 = node1.parent + + while node0 != node1: + node0, node1 = node0.parent, node1.parent + + return node0 @enable_executor_hook def lca_wrapper(executor, tree, node0, node1): result = executor.run( - functools.partial(lca, must_find_node(tree, node0), - must_find_node(tree, node1))) + functools.partial(lca, must_find_node(tree, node0), must_find_node(tree, node1)) + ) if result is None: - raise TestFailure('Result can\'t be None') + raise TestFailure("Result can't be None") return result.data -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('lowest_common_ancestor_with_parent.py', - 'lowest_common_ancestor.tsv', - lca_wrapper)) + generic_test.generic_test_main( + "lowest_common_ancestor_with_parent.py", + "lowest_common_ancestor.tsv", + lca_wrapper, + ) + ) diff --git a/epi_judge_python/lru_cache.py b/epi_judge_python/lru_cache.py index 3b26353c6..7b4ca906d 100644 --- a/epi_judge_python/lru_cache.py +++ b/epi_judge_python/lru_cache.py @@ -1,23 +1,31 @@ +from typing import OrderedDict from test_framework import generic_test from test_framework.test_failure import TestFailure class LruCache: def __init__(self, capacity: int) -> None: - # TODO - you fill in here. - return + self.capacity = capacity + self.ordered_dict = OrderedDict() def lookup(self, isbn: int) -> int: - # TODO - you fill in here. - return 0 + if isbn not in self.ordered_dict: + return -1 + price = self.ordered_dict.pop(isbn) + self.ordered_dict[isbn] = price + return price def insert(self, isbn: int, price: int) -> None: - # TODO - you fill in here. - return + if isbn in self.ordered_dict: + self.ordered_dict.move_to_end(isbn) + else: + self.ordered_dict[isbn] = price + + if len(self.ordered_dict) > self.capacity: + self.ordered_dict.popitem(last=False) def erase(self, isbn: int) -> bool: - # TODO - you fill in here. - return True + return self.ordered_dict.pop(isbn, None) is not None def lru_cache_tester(commands): diff --git a/epi_judge_python/matrix_rotation.py b/epi_judge_python/matrix_rotation.py index 76605a4bf..6537b7fa3 100644 --- a/epi_judge_python/matrix_rotation.py +++ b/epi_judge_python/matrix_rotation.py @@ -3,8 +3,28 @@ from test_framework import generic_test +# time: O(n^2), matrix is of size n * n +# space: O(n^2) +# def rotate_matrix(square_matrix: List[List[int]]) -> None: +# length = length +# rot_matrix = [[0] * length for _ in range(length)] +# for row in range(length): +# for col in range(length): +# rot_matrix[col][length - row] = square_matrix[row][col] +# square_matrix[:] = rot_matrix +# return + +# time: O(n^2), matrix is of size n * n +# space: O(1) def rotate_matrix(square_matrix: List[List[int]]) -> None: - # TODO - you fill in here. + l = len(square_matrix) - 1 + for i in range(len(square_matrix) // 2): + for j in range(i, l - i): + topLeft = square_matrix[i][j] + square_matrix[i][j] = square_matrix[-j - 1][i] + square_matrix[-j - 1][i] = square_matrix[-i - 1][-j - 1] + square_matrix[-i - 1][-j - 1] = square_matrix[j][-i - 1] + square_matrix[j][-i - 1] = topLeft return @@ -13,8 +33,9 @@ def rotate_matrix_wrapper(square_matrix): return square_matrix -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('matrix_rotation.py', - 'matrix_rotation.tsv', - rotate_matrix_wrapper)) + generic_test.generic_test_main( + "matrix_rotation.py", "matrix_rotation.tsv", rotate_matrix_wrapper + ) + ) diff --git a/epi_judge_python/nearest_repeated_entries.py b/epi_judge_python/nearest_repeated_entries.py index fb634bc83..1785b31c6 100644 --- a/epi_judge_python/nearest_repeated_entries.py +++ b/epi_judge_python/nearest_repeated_entries.py @@ -1,11 +1,28 @@ -from typing import List +from typing import Dict, List +import typing from test_framework import generic_test +# time: O(n) +# space: O(n) def find_nearest_repetition(paragraph: List[str]) -> int: - # TODO - you fill in here. - return 0 + nearest_rep = float('inf') + word_occurance: Dict[str, int] = dict() + + for i, word in enumerate(paragraph): + if word in word_occurance: + dist = i - word_occurance.get(word, 0) + if dist == 1: + return dist + if dist < nearest_rep: + nearest_rep = dist + word_occurance[word] = i + + if nearest_rep == float('inf'): + nearest_rep = -1 + + return typing.cast(int, nearest_rep) if __name__ == '__main__': diff --git a/epi_judge_python/next_permutation.py b/epi_judge_python/next_permutation.py index 8584b619c..48d5b2c14 100644 --- a/epi_judge_python/next_permutation.py +++ b/epi_judge_python/next_permutation.py @@ -3,9 +3,20 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def next_permutation(perm: List[int]) -> List[int]: - # TODO - you fill in here. - return [] + desc_idx = len(perm) - 2 + while desc_idx >= 0 and perm[desc_idx + 1] <= perm[desc_idx]: + desc_idx -= 1 + if desc_idx == -1: + return [] + for i in reversed(range(desc_idx + 1, len(perm))): + if perm[i] > perm[desc_idx]: + perm[desc_idx], perm[i] = perm[i], perm[desc_idx] + break + perm[desc_idx + 1:] = list(reversed(perm[desc_idx + 1:])) + return perm if __name__ == '__main__': diff --git a/epi_judge_python/nodemon.json b/epi_judge_python/nodemon.json new file mode 100644 index 000000000..29f80fc37 --- /dev/null +++ b/epi_judge_python/nodemon.json @@ -0,0 +1,5 @@ +{ + "events": { + "start": "clear" + } +} diff --git a/epi_judge_python/nonuniform_random_number.py b/epi_judge_python/nonuniform_random_number.py index 06b8fd2f8..5075758f8 100644 --- a/epi_judge_python/nonuniform_random_number.py +++ b/epi_judge_python/nonuniform_random_number.py @@ -1,6 +1,9 @@ import collections import functools import math +import random +import itertools +import bisect from typing import List from test_framework import generic_test @@ -8,10 +11,17 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n) +# space: O(1) def nonuniform_random_number_generation(values: List[int], probabilities: List[float]) -> int: - # TODO - you fill in here. - return 0 + rand = random.random() + prob_sum = 0 + i = 0 + while prob_sum < rand: + prob_sum += probabilities[i] + i += 1 + return values[i - 1] @enable_executor_hook diff --git a/epi_judge_python/offline_sampling.py b/epi_judge_python/offline_sampling.py index 807c20c98..3298d4c8c 100644 --- a/epi_judge_python/offline_sampling.py +++ b/epi_judge_python/offline_sampling.py @@ -1,4 +1,5 @@ import functools +import random from typing import List from test_framework import generic_test @@ -8,8 +9,44 @@ from test_framework.test_utils import enable_executor_hook +# repeatedly pick a random number from A (pick again if number is picked twice) +# time: O(k) +# space: O(k) +""" def random_sampling(k: int, A: List[int]) -> None: - # TODO - you fill in here. + result = [] + for _ in range(k): + pick = None + while pick == None or pick in result: + pick = random.choice(A) + result.append(pick) + A[:] = result + return +""" + +# repeatedly pick a random number from A and swap it to the start of A +# time: O(k) +# space: O(1) +""" +def random_sampling(k: int, A: List[int]) -> None: + for i in range(k): + pick = random.randint(i, len(A) - 1) + A[i], A[pick] = A[pick], A[i] + return +""" + +# optimize for k > n/2 +# time: O(min(k, n-k)) +# space: O(1) +def random_sampling(k: int, A: List[int]) -> None: + if k <= len(A) / 2: + for i in range(k): + pick = random.randint(i, len(A) - 1) + A[i], A[pick] = A[pick], A[i] + else: + for i in range(len(A) - k): + pick = random.randint(0, len(A) - 1 - i) + A[len(A) - 1 -i], A[pick] = A[pick], A[len(A) - 1 -i] return diff --git a/epi_judge_python/online_median.py b/epi_judge_python/online_median.py index 1ca739006..71d4622cd 100644 --- a/epi_judge_python/online_median.py +++ b/epi_judge_python/online_median.py @@ -1,11 +1,28 @@ from typing import Iterator, List +import heapq from test_framework import generic_test +# time: O(n log n); O(log n) per entry +# space: O(n) def online_median(sequence: Iterator[int]) -> List[float]: - # TODO - you fill in here. - return [] + result = [] + upper_min_heap = [] + lower_max_heap = [] + + for num in sequence: + heapq.heappush(lower_max_heap, -heapq.heappushpop(upper_min_heap, num)) + + if len(lower_max_heap) > len(upper_min_heap): + heapq.heappush(upper_min_heap, -heapq.heappop(lower_max_heap)) + + if len(lower_max_heap) == len(upper_min_heap): + result.append((-lower_max_heap[0] + upper_min_heap[0]) / 2) + else: + result.append(upper_min_heap[0]) + + return result def online_median_wrapper(sequence): diff --git a/epi_judge_python/online_sampling.py b/epi_judge_python/online_sampling.py index 1edd4e5cb..8a81f7eaf 100644 --- a/epi_judge_python/online_sampling.py +++ b/epi_judge_python/online_sampling.py @@ -1,4 +1,6 @@ import functools +import random +import itertools from typing import Iterator, List from test_framework import generic_test @@ -9,9 +11,17 @@ # Assumption: there are at least k elements in the stream. +# time: O(n) +# space: O(k) def online_random_sample(stream: Iterator[int], k: int) -> List[int]: - # TODO - you fill in here. - return [] + sample = list(itertools.islice(stream, k)) + + for i, el in enumerate(stream): + pick = random.randint(0, k + i) + if pick < k: + sample[pick] = el + + return sample @enable_executor_hook diff --git a/epi_judge_python/parity.py b/epi_judge_python/parity.py index b50746894..41f78dfda 100644 --- a/epi_judge_python/parity.py +++ b/epi_judge_python/parity.py @@ -1,9 +1,60 @@ from test_framework import generic_test +# check every bit (brute force) +# time complexity: O(n) +""" def parity(x: int) -> int: - # TODO - you fill in here. - return 0 + result = 0 + for i in range(0, 64): + result ^= (x >> i) & 1 + return result +""" + +# only iterate for each of the k set bits +# time complexity: O(k) +""" +def parity(x: int) -> int: + result = 0 + while x != 0: + result ^= 1 + x &= (x-1) + return result +""" + +# split up words into chunks of size L and cache partial solutions +# time complexity: O(n/L) (initialization of the cache not included) +""" +def partialParity(x: int) -> int: + result = 0 + while x != 0: + result ^= 1 + x &= (x-1) + return result + +CACHE = [] +def parity(x: int) -> int: + if len(CACHE) == 0: + for i in range(2**16): + CACHE.append(partialParity(i)) + + result = 0 + while x != 0: + result ^= CACHE[x & 0xffff] + x >>= 16 + return result +""" + +# use associativity of parity +# time complexity: O(log n) +def parity(x: int) -> int: + x ^= x >> 32 + x ^= x >> 16 + x ^= x >> 8 + x ^= x >> 4 + x ^= x >> 2 + x ^= x >> 1 + return x & 1 # only last bit is relevant if __name__ == '__main__': diff --git a/epi_judge_python/pascal_triangle.py b/epi_judge_python/pascal_triangle.py index 3e74e43c0..da074da42 100644 --- a/epi_judge_python/pascal_triangle.py +++ b/epi_judge_python/pascal_triangle.py @@ -3,13 +3,19 @@ from test_framework import generic_test +# time: O(n^2) +# space: O(1) def generate_pascal_triangle(n: int) -> List[List[int]]: - # TODO - you fill in here. - return [] + triangle = [[1] * x for x in range(1, n + 1)] + for i in range(n): + for j in range(1, i): + triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j] + return triangle -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('pascal_triangle.py', - 'pascal_triangle.tsv', - generate_pascal_triangle)) + generic_test.generic_test_main( + "pascal_triangle.py", "pascal_triangle.tsv", generate_pascal_triangle + ) + ) diff --git a/epi_judge_python/path_sum.py b/epi_judge_python/path_sum.py index 9b7981a96..a5c3c11c7 100644 --- a/epi_judge_python/path_sum.py +++ b/epi_judge_python/path_sum.py @@ -2,12 +2,18 @@ from test_framework import generic_test -def has_path_sum(tree: BinaryTreeNode, remaining_weight: int) -> bool: - # TODO - you fill in here. - return True +# time: O(n) +# space: O(h) +def has_path_sum(tree: BinaryTreeNode | None, remaining_weight: int) -> bool: + if not tree: + return False + if not tree.left and not tree.right: + return remaining_weight == tree.data -if __name__ == '__main__': - exit( - generic_test.generic_test_main('path_sum.py', 'path_sum.tsv', - has_path_sum)) + new_rem = remaining_weight - tree.data + return has_path_sum(tree.left, new_rem) or has_path_sum(tree.right, new_rem) + + +if __name__ == "__main__": + exit(generic_test.generic_test_main("path_sum.py", "path_sum.tsv", has_path_sum)) diff --git a/epi_judge_python/pivot_list.py b/epi_judge_python/pivot_list.py index 3bfa71496..ae12d8aea 100644 --- a/epi_judge_python/pivot_list.py +++ b/epi_judge_python/pivot_list.py @@ -7,9 +7,27 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n) +# space: O(1) def list_pivoting(l: ListNode, x: int) -> Optional[ListNode]: - # TODO - you fill in here. - return None + dummy_head_lower = lower = ListNode() + dummy_head_pivot = pivot = ListNode() + dummy_head_greater = greater = ListNode() + while l: + if l.data < x: + lower.next = l + lower = lower.next + elif l.data == x: + pivot.next = l + pivot = pivot.next + else: + greater.next = l + greater = greater.next + l = l.next + greater.next = None + pivot.next = dummy_head_greater.next + lower.next = dummy_head_pivot.next + return dummy_head_lower.next def linked_to_list(l): @@ -36,18 +54,20 @@ def list_pivoting_wrapper(executor, l, x): mode = 1 elif mode == 0: if i < x: - raise TestFailure('List is not pivoted') + raise TestFailure("List is not pivoted") elif i > x: mode = 1 else: if i <= x: - raise TestFailure('List is not pivoted') + raise TestFailure("List is not pivoted") if sorted(original) != sorted(pivoted): - raise TestFailure('Result list contains different values') + raise TestFailure("Result list contains different values") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('pivot_list.py', 'pivot_list.tsv', - list_pivoting_wrapper)) + generic_test.generic_test_main( + "pivot_list.py", "pivot_list.tsv", list_pivoting_wrapper + ) + ) diff --git a/epi_judge_python/playground/playground.py b/epi_judge_python/playground/playground.py new file mode 100644 index 000000000..e69de29bb diff --git a/epi_judge_python/power_x_y.py b/epi_judge_python/power_x_y.py index 83c2dfdb5..e482f39ed 100644 --- a/epi_judge_python/power_x_y.py +++ b/epi_judge_python/power_x_y.py @@ -1,11 +1,34 @@ from test_framework import generic_test +# repeatedly multiply by x +# time complexity: O(2^n) +""" def power(x: float, y: int) -> float: - # TODO - you fill in here. - return 0.0 + result = 1 + for _ in range(abs(y)): + result *= x + if y < 0: + result = 1/result; + return result +""" + +# reduce multiplications by grouping terms +# time complexity: O(n) +def power(x: float, y: int) -> float: + result = 1.0 + if y < 0: + x = 1/x + y = abs(y) + while y: + if y & 1: + result *= x + x *= x + y >>= 1 + return result if __name__ == '__main__': exit(generic_test.generic_test_main('power_x_y.py', 'power_x_y.tsv', power)) + diff --git a/epi_judge_python/prime_sieve.py b/epi_judge_python/prime_sieve.py index 538d26b57..5105182ff 100644 --- a/epi_judge_python/prime_sieve.py +++ b/epi_judge_python/prime_sieve.py @@ -1,12 +1,38 @@ from typing import List +import math from test_framework import generic_test -# Given n, return all primes up to and including n. +# brute force +# time: O(n * sqrt(n)) +# space: O(1) +""" def generate_primes(n: int) -> List[int]: - # TODO - you fill in here. - return [] + primes = [] + for i in range(2, n + 1): + isPrime = True + for j in range(2, math.floor(math.sqrt(i)) + 1): + if i % j == 0: + isPrime = False + break; + if isPrime: + primes.append(i) + return primes +""" + +# remove multiples of primes +# time: O(n log log n) +# space: O(n) +def generate_primes(n: int) -> List[int]: + is_prime = [False] * 2 + [True] * n + primes = [] + for i in range(2, n + 1): + if is_prime[i]: + primes.append(i) + for j in range(2 * i, n + 1, i): + is_prime[j] = False + return primes if __name__ == '__main__': diff --git a/epi_judge_python/primitive_divide.py b/epi_judge_python/primitive_divide.py index 4bf90cda8..47a904efa 100644 --- a/epi_judge_python/primitive_divide.py +++ b/epi_judge_python/primitive_divide.py @@ -1,9 +1,29 @@ from test_framework import generic_test +# repeatedly subtract y from x and count +# time complexity: very high, takes x/y iterations +""" def divide(x: int, y: int) -> int: - # TODO - you fill in here. - return 0 + result = 0 + while x >= y: + x -= y + result += 1 + return result +""" + +# repeatedly subtract the largest 2^(k)y smaller than x +# time complexity: O(n) +def divide(x: int, y: int) -> int: + result = 0 + power = 32 + + while x >= y: + while (y << power) > x: + power -= 1 + x -= y << power + result += 1 << power + return result if __name__ == '__main__': diff --git a/epi_judge_python/primitive_multiply.py b/epi_judge_python/primitive_multiply.py index f36198e87..75c0ab1a3 100644 --- a/epi_judge_python/primitive_multiply.py +++ b/epi_judge_python/primitive_multiply.py @@ -1,12 +1,32 @@ from test_framework import generic_test +def add(a: int, b: int) -> int: + return a if b == 0 else add(a^b, (a&b) << 1) +# repeatedly add +# time complexity: O(2^n) +""" def multiply(x: int, y: int) -> int: - # TODO - you fill in here. - return 0 + result = 0 + for _ in range(x): + result = add(y, result) + return result +""" + +# add 2^(k)y for every set bit in x +# time complexity: O(n^2) +def multiply(x: int, y: int) -> int: + result = 0 + while x: + if x & 1: + result = add(result, y) + x >>= 1 + y <<= 1 + return result if __name__ == '__main__': exit( generic_test.generic_test_main('primitive_multiply.py', 'primitive_multiply.tsv', multiply)) + diff --git a/epi_judge_python/queue_from_stacks.py b/epi_judge_python/queue_from_stacks.py index 81ea76dcc..c4a7d8392 100644 --- a/epi_judge_python/queue_from_stacks.py +++ b/epi_judge_python/queue_from_stacks.py @@ -3,13 +3,19 @@ class Queue: + enqStack = [] + deqStack = [] + + # time: O(1) def enqueue(self, x: int) -> None: - # TODO - you fill in here. - return + self.enqStack.append(x) + # time: O(m) for m operations def dequeue(self) -> int: - # TODO - you fill in here. - return 0 + if not self.deqStack: + while self.enqStack: + self.deqStack.append(self.enqStack.pop()) + return self.deqStack.pop() def queue_tester(ops): @@ -17,22 +23,25 @@ def queue_tester(ops): q = Queue() for (op, arg) in ops: - if op == 'Queue': + if op == "Queue": q = Queue() - elif op == 'enqueue': + elif op == "enqueue": q.enqueue(arg) - elif op == 'dequeue': + elif op == "dequeue": result = q.dequeue() if result != arg: - raise TestFailure('Dequeue: expected ' + str(arg) + - ', got ' + str(result)) + raise TestFailure( + "Dequeue: expected " + str(arg) + ", got " + str(result) + ) else: - raise RuntimeError('Unsupported queue operation: ' + op) + raise RuntimeError("Unsupported queue operation: " + op) except IndexError: - raise TestFailure('Unexpected IndexError exception') + raise TestFailure("Unexpected IndexError exception") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('queue_from_stacks.py', - 'queue_from_stacks.tsv', queue_tester)) + generic_test.generic_test_main( + "queue_from_stacks.py", "queue_from_stacks.tsv", queue_tester + ) + ) diff --git a/epi_judge_python/queue_with_max.py b/epi_judge_python/queue_with_max.py index 6c498a735..6a50b3414 100644 --- a/epi_judge_python/queue_with_max.py +++ b/epi_judge_python/queue_with_max.py @@ -1,19 +1,29 @@ +import collections from test_framework import generic_test from test_framework.test_failure import TestFailure class QueueWithMax: + entries = collections.deque() + potential_max = collections.deque() + + # time: O(1) (amortized) def enqueue(self, x: int) -> None: - # TODO - you fill in here. - return + self.entries.append(x) + while self.potential_max and self.potential_max[-1] < x: + self.potential_max.pop() + self.potential_max.append(x) + # time: O(1) def dequeue(self) -> int: - # TODO - you fill in here. - return 0 + value = self.entries.popleft() + if value == self.potential_max[0]: + self.potential_max.popleft() + return value + # time: O(1) def max(self) -> int: - # TODO - you fill in here. - return 0 + return self.potential_max[0] def queue_tester(ops): @@ -22,27 +32,31 @@ def queue_tester(ops): q = QueueWithMax() for (op, arg) in ops: - if op == 'QueueWithMax': + if op == "QueueWithMax": q = QueueWithMax() - elif op == 'enqueue': + elif op == "enqueue": q.enqueue(arg) - elif op == 'dequeue': + elif op == "dequeue": result = q.dequeue() if result != arg: - raise TestFailure('Dequeue: expected ' + str(arg) + - ', got ' + str(result)) - elif op == 'max': + raise TestFailure( + "Dequeue: expected " + str(arg) + ", got " + str(result) + ) + elif op == "max": result = q.max() if result != arg: - raise TestFailure('Max: expected ' + str(arg) + ', got ' + - str(result)) + raise TestFailure( + "Max: expected " + str(arg) + ", got " + str(result) + ) else: - raise RuntimeError('Unsupported queue operation: ' + op) + raise RuntimeError("Unsupported queue operation: " + op) except IndexError: - raise TestFailure('Unexpected IndexError exception') + raise TestFailure("Unexpected IndexError exception") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('queue_with_max.py', - 'queue_with_max.tsv', queue_tester)) + generic_test.generic_test_main( + "queue_with_max.py", "queue_with_max.tsv", queue_tester + ) + ) diff --git a/epi_judge_python/random_permutation.py b/epi_judge_python/random_permutation.py index 075f1bcc2..1bb947e7f 100644 --- a/epi_judge_python/random_permutation.py +++ b/epi_judge_python/random_permutation.py @@ -1,6 +1,7 @@ import copy import functools import math +import random from typing import List from test_framework import generic_test @@ -9,9 +10,14 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n) +# space: O(1) def compute_random_permutation(n: int) -> List[int]: - # TODO - you fill in here. - return [] + permutation = list(range(n)) + for i in range(n - 1): + random_idx = random.randint(i, n - 1) + permutation[random_idx], permutation[i] = permutation[i], permutation[random_idx] + return permutation @enable_executor_hook diff --git a/epi_judge_python/random_subset.py b/epi_judge_python/random_subset.py index 7fa4013cb..cc084c7f5 100644 --- a/epi_judge_python/random_subset.py +++ b/epi_judge_python/random_subset.py @@ -1,4 +1,5 @@ import functools +import random from typing import List from test_framework import generic_test @@ -8,9 +9,27 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n) +# space: O(n) +""" def random_subset(n: int, k: int) -> List[int]: - # TODO - you fill in here. - return [] + subset = list(range(n)) + for i in range(k): + random_idx = random.randint(i, n - 1) + subset[random_idx], subset[i] = subset[i], subset[random_idx] + return subset[:k] +""" + +# time: O(k) +# space: O(k) +def random_subset(n: int, k: int) -> List[int]: + subset = dict() + for i in range(k): + random_idx = random.randint(i, n - 1) + val_i = subset.get(i, i) + val_random_idx = subset.get(random_idx, random_idx) + subset[random_idx], subset[i] = val_i, val_random_idx + return [subset[i] for i in range(k)] @enable_executor_hook diff --git a/epi_judge_python/real_square_root.py b/epi_judge_python/real_square_root.py index d96c26649..4de6c82fb 100644 --- a/epi_judge_python/real_square_root.py +++ b/epi_judge_python/real_square_root.py @@ -1,9 +1,23 @@ from test_framework import generic_test +import math +# time: O(log (x / s)), where s is the tolerance (precision) +# space: O(1) def square_root(x: float) -> float: - # TODO - you fill in here. - return 0.0 + lower = 1 if x >= 1 else x + upper = x if x >= 1 else 1 + + while True: + mid = lower + (upper - lower) / 2 + square = mid * mid + + if math.isclose(square, x): + return mid + elif square < x: + lower = mid + else: + upper = mid if __name__ == '__main__': diff --git a/epi_judge_python/rectangle_intersection.py b/epi_judge_python/rectangle_intersection.py index d8898b474..11cae2504 100644 --- a/epi_judge_python/rectangle_intersection.py +++ b/epi_judge_python/rectangle_intersection.py @@ -5,10 +5,13 @@ Rect = collections.namedtuple('Rect', ('x', 'y', 'width', 'height')) - +# time O(1) def intersect_rectangle(r1: Rect, r2: Rect) -> Rect: - # TODO - you fill in here. - return Rect(0, 0, 0, 0) + width = min(r1.x + r1.width, r2.x + r2.width) - max(r1.x, r2.x) + height = min(r1.y + r1.height, r2.y + r2.height) - max(r1.y, r2.y) + if width >= 0 and height >= 0: + return Rect(max(r1.x, r2.x), max(r1.y, r2.y), width, height) + return Rect(0, 0, -1, -1) def intersect_rectangle_wrapper(r1, r2): diff --git a/epi_judge_python/remove_duplicates_from_sorted_list.py b/epi_judge_python/remove_duplicates_from_sorted_list.py index 12fa03de6..35b2fd6ca 100644 --- a/epi_judge_python/remove_duplicates_from_sorted_list.py +++ b/epi_judge_python/remove_duplicates_from_sorted_list.py @@ -4,13 +4,22 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def remove_duplicates(L: ListNode) -> Optional[ListNode]: - # TODO - you fill in here. - return None + cur = L + while cur: + while cur.next and cur.data == cur.next.data: + cur.next = cur.next.next + cur = cur.next + return L -if __name__ == '__main__': +if __name__ == "__main__": exit( generic_test.generic_test_main( - 'remove_duplicates_from_sorted_list.py', - 'remove_duplicates_from_sorted_list.tsv', remove_duplicates)) + "remove_duplicates_from_sorted_list.py", + "remove_duplicates_from_sorted_list.tsv", + remove_duplicates, + ) + ) diff --git a/epi_judge_python/replace_and_remove.py b/epi_judge_python/replace_and_remove.py index b52e43376..763ec9d99 100644 --- a/epi_judge_python/replace_and_remove.py +++ b/epi_judge_python/replace_and_remove.py @@ -5,9 +5,61 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n^2) +# space: O(1) +# def replace_and_remove(size: int, s: List[str]) -> int: +# i = 0 +# while i < len(s): +# if s[i] == "b": +# del s[i] +# size -= 1 +# elif s[i] == "a": +# s[i] = "d" +# s.insert(i, "d") +# size += 1 +# i += 2 +# else: +# i += 1 +# return size + + +# time: O(n) +# space: O(n) +# def replace_and_remove(size: int, s: List[str]) -> int: +# result = [] +# for i in range(size): +# if s[i] == "b": +# continue +# if s[i] == "a": +# result.append("d") +# result.append("d") +# else: +# result.append(s[i]) +# s[:] = result +# return len(result) + + +# time: O(n) +# space: O(1) def replace_and_remove(size: int, s: List[str]) -> int: - # TODO - you fill in here. - return 0 + write_idx = 0 + a_count = 0 + for i in range(size): + if s[i] != "b": + s[write_idx] = s[i] + write_idx += 1 + if s[i] == "a": + a_count += 1 + write_idx += a_count - 1 + size = write_idx + 1 + for i in reversed(range(size - a_count)): + if s[i] == "a": + s[write_idx - 1 : write_idx + 1] = "dd" + write_idx -= 2 + else: + s[write_idx] = s[i] + write_idx -= 1 + return size @enable_executor_hook @@ -16,8 +68,11 @@ def replace_and_remove_wrapper(executor, size, s): return s[:res_size] -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('replace_and_remove.py', - 'replace_and_remove.tsv', - replace_and_remove_wrapper)) + generic_test.generic_test_main( + "replace_and_remove.py", + "replace_and_remove.tsv", + replace_and_remove_wrapper, + ) + ) diff --git a/epi_judge_python/reverse_bits.py b/epi_judge_python/reverse_bits.py index 83b413c6e..79829068b 100644 --- a/epi_judge_python/reverse_bits.py +++ b/epi_judge_python/reverse_bits.py @@ -1,9 +1,37 @@ from test_framework import generic_test +# brute force +# time complexity: O(n) +""" def reverse_bits(x: int) -> int: - # TODO - you fill in here. - return 0 + word_size = 64 + for i in range(word_size//2): + if (x >> i) & 1 != (x >> (word_size - 1 - i)) & 1: + x ^= (1 << i) | (1 << (word_size - 1 - i)) + return x +""" + +# split word into chunks of size L and cache partial results +# time complexity: O(n/L) (filling the cache not included) +CACHE = [] +def reverse_bits(x: int) -> int: + word_size = 64 + chunk_size = 16 + + if len(CACHE) == 0: + for i in range(2 ** chunk_size): + for j in range(chunk_size//2): + if (i >> j) & 1 != (i >> (chunk_size - 1 - j)) & 1: + i ^= (1 << j) | (1 << (chunk_size - 1 - j)) + CACHE.append(i) + + res = 0 + nr_chunks = word_size // chunk_size + + for i in range(nr_chunks): + res |= CACHE[x >> (i * 16) & 0xFFFF] << (nr_chunks - 1 - i) * 16 + return res if __name__ == '__main__': diff --git a/epi_judge_python/reverse_digits.py b/epi_judge_python/reverse_digits.py index 51937a76b..ab36ae2d3 100644 --- a/epi_judge_python/reverse_digits.py +++ b/epi_judge_python/reverse_digits.py @@ -1,9 +1,25 @@ from test_framework import generic_test +# string manipulation +# time complexity: O(n) +""" def reverse(x: int) -> int: - # TODO - you fill in here. - return 0 + sign = -1 if x < 0 else 1 + return sign * int(str(abs(x))[::-1]) +""" + +# using mod 10 +# time complexity: O(n) +def reverse(x: int) -> int: + result = 0 + sign = -1 if x < 0 else 1 + x = abs(x) + while x > 0: + result *= 10 + result += x % 10 + x //= 10 + return sign * result if __name__ == '__main__': diff --git a/epi_judge_python/reverse_sublist.py b/epi_judge_python/reverse_sublist.py index 77cf7fbb3..4f515c177 100644 --- a/epi_judge_python/reverse_sublist.py +++ b/epi_judge_python/reverse_sublist.py @@ -3,14 +3,23 @@ from list_node import ListNode from test_framework import generic_test +# time: O(n) +# space: O(1) +def reverse_sublist(L: ListNode, start: int, finish: int) -> Optional[ListNode]: + dummy_head = before_sub = ListNode(0, L) + for _ in range(1, start): + before_sub = before_sub.next -def reverse_sublist(L: ListNode, start: int, - finish: int) -> Optional[ListNode]: - # TODO - you fill in here. - return None + new_sub_tail = before_sub.next + for _ in range(start, finish): + tmp = new_sub_tail.next + new_sub_tail.next, tmp.next, before_sub.next = tmp.next, before_sub.next, tmp + return dummy_head.next -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('reverse_sublist.py', - 'reverse_sublist.tsv', reverse_sublist)) + generic_test.generic_test_main( + "reverse_sublist.py", "reverse_sublist.tsv", reverse_sublist + ) + ) diff --git a/epi_judge_python/reverse_words.py b/epi_judge_python/reverse_words.py index b83046c89..f107ecf51 100644 --- a/epi_judge_python/reverse_words.py +++ b/epi_judge_python/reverse_words.py @@ -6,9 +6,25 @@ # Assume s is a list of strings, each of which is of length 1, e.g., # ['r', 'a', 'm', ' ', 'i', 's', ' ', 'c', 'o', 's', 't', 'l', 'y']. +# time: O(n) +# space: O(n) +# def reverse_words(s): +# s[:] = list(" ".join(reversed("".join(s).split(" ")))) + +# time: O(n) +# space: O(1) def reverse_words(s): - # TODO - you fill in here. - return + def reverse(arr, low, high): + for i in range(low, (low + high + 1) // 2): + arr[i], arr[high + low - i] = arr[high + low - i], arr[i] + + reverse(s, 0, len(s) - 1) + start = end = 0 + while end < len(s): + while end < len(s) and s[end] != " ": + end += 1 + reverse(s, start, end - 1) + start = end = end + 1 @enable_executor_hook @@ -17,10 +33,12 @@ def reverse_words_wrapper(executor, s): executor.run(functools.partial(reverse_words, s_copy)) - return ''.join(s_copy) + return "".join(s_copy) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('reverse_words.py', 'reverse_words.tsv', - reverse_words_wrapper)) + generic_test.generic_test_main( + "reverse_words.py", "reverse_words.tsv", reverse_words_wrapper + ) + ) diff --git a/epi_judge_python/roman_to_integer.py b/epi_judge_python/roman_to_integer.py index 04ca4d0c0..1d04cc840 100644 --- a/epi_judge_python/roman_to_integer.py +++ b/epi_judge_python/roman_to_integer.py @@ -1,13 +1,25 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def roman_to_integer(s: str) -> int: - # TODO - you fill in here. - return 0 + numerals = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} + sum = 0 + i = 0 + while i < len(s): + if i + 1 == len(s) or numerals[s[i]] >= numerals[s[i + 1]]: + sum += numerals[s[i]] + i += 1 + else: + sum += numerals[s[i + 1]] - numerals[s[i]] + i += 2 + return sum -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('roman_to_integer.py', - 'roman_to_integer.tsv', - roman_to_integer)) + generic_test.generic_test_main( + "roman_to_integer.py", "roman_to_integer.tsv", roman_to_integer + ) + ) diff --git a/epi_judge_python/run_length_compression.py b/epi_judge_python/run_length_compression.py index c6cbc4a97..0da46a1d2 100644 --- a/epi_judge_python/run_length_compression.py +++ b/epi_judge_python/run_length_compression.py @@ -2,25 +2,43 @@ from test_framework.test_failure import TestFailure +# time: O(n) +# space: O(n) def decoding(s: str) -> str: - # TODO - you fill in here. - return '' - - + result = [] + start = i = 0 + while i < len(s): + while s[i].isdigit(): + i += 1 + result.append(s[i] * int(s[start:i])) + i += 1 + start = i + return "".join(result) + + +# time: O(n) +# space: O(n) def encoding(s: str) -> str: - # TODO - you fill in here. - return '' + result = [] + start = cur = 0 + while cur < len(s): + while cur < len(s) and s[start] == s[cur]: + cur += 1 + result.append(str(cur - start) + s[cur - 1]) + start = cur + return "".join(result) def rle_tester(encoded, decoded): if decoding(encoded) != decoded: - raise TestFailure('Decoding failed') + raise TestFailure("Decoding failed") if encoding(decoded) != encoded: - raise TestFailure('Encoding failed') + raise TestFailure("Encoding failed") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('run_length_compression.py', - 'run_length_compression.tsv', - rle_tester)) + generic_test.generic_test_main( + "run_length_compression.py", "run_length_compression.tsv", rle_tester + ) + ) diff --git a/epi_judge_python/search_entry_equal_to_index.py b/epi_judge_python/search_entry_equal_to_index.py index f913d7e36..50cd1b955 100644 --- a/epi_judge_python/search_entry_equal_to_index.py +++ b/epi_judge_python/search_entry_equal_to_index.py @@ -6,9 +6,23 @@ from test_framework.test_utils import enable_executor_hook +# time: O(log n) +# space: O(1) def search_entry_equal_to_its_index(A: List[int]) -> int: - # TODO - you fill in here. - return 0 + lower = 0 + upper = len(A) - 1 + + while lower <= upper: + mid = lower + (upper - lower) // 2 + + if mid == A[mid]: + return mid + elif mid < A[mid]: + upper = mid - 1 + else: + lower = mid + 1 + + return -1 @enable_executor_hook diff --git a/epi_judge_python/search_first_key.py b/epi_judge_python/search_first_key.py index f85aa9608..2ea2b4424 100644 --- a/epi_judge_python/search_first_key.py +++ b/epi_judge_python/search_first_key.py @@ -1,11 +1,32 @@ from typing import List +import bisect from test_framework import generic_test +# time: O(log n) +# space: O(1) +# def search_first_of_k(A: List[int], k: int) -> int: +# i = bisect.bisect_left(A, k) +# return i if (i < len(A) and A[i] == k) else -1 + + +# time: O(log n) +# space: O(1) def search_first_of_k(A: List[int], k: int) -> int: - # TODO - you fill in here. - return 0 + lower = 0 + upper = len(A) - 1 + + while lower < upper: + mid = lower + (upper - lower) // 2 + if A[mid] < k: + lower = mid + 1 + elif A[mid] > k: + upper = mid - 1 + else: + upper = mid + + return lower if (lower < len(A) and A[lower] == k) else -1 if __name__ == '__main__': diff --git a/epi_judge_python/search_for_min_max_in_array.py b/epi_judge_python/search_for_min_max_in_array.py index 318103e99..b6966de4f 100644 --- a/epi_judge_python/search_for_min_max_in_array.py +++ b/epi_judge_python/search_for_min_max_in_array.py @@ -7,9 +7,23 @@ MinMax = collections.namedtuple('MinMax', ('smallest', 'largest')) +# time: O(n) +# space: O(1) def find_min_max(A: List[int]) -> MinMax: - # TODO - you fill in here. - return MinMax(0, 0) + lowest = float('inf') + highest = -float('inf') + + for i in range(0, len(A) - 1, 2): + min_candidate, max_candidate = sorted([A[i], A[i + 1]]) + lowest = min(lowest, min_candidate) + highest = max(highest, max_candidate) + + if len(A) % 2: + lowest = min(lowest, A[-1]) + highest = max(highest, A[-1]) + + + return MinMax(lowest, highest) def res_printer(prop, value): diff --git a/epi_judge_python/search_for_missing_element.py b/epi_judge_python/search_for_missing_element.py index b474530a8..906260798 100644 --- a/epi_judge_python/search_for_missing_element.py +++ b/epi_judge_python/search_for_missing_element.py @@ -7,10 +7,68 @@ DuplicateAndMissing = collections.namedtuple('DuplicateAndMissing', ('duplicate', 'missing')) +# time: O(n log n) +# space: O(1) +# def find_duplicate_missing(A: List[int]) -> DuplicateAndMissing: +# A = sorted(A) +# +# duplicate = None +# missing = None +# +# for i in range(len(A)): +# if i + 1 == len(A): +# if missing is None: +# missing = A[i] + 1 +# else: +# diff = A[i + 1] - A[i] +# if diff == 0: +# duplicate = A[i] +# elif diff == 2: +# missing = A[i] + 1 +# +# return DuplicateAndMissing(duplicate, missing) + +# time: O(n) +# space: O(n) +# def find_duplicate_missing(A: List[int]) -> DuplicateAndMissing: +# counts = [0] * len(A) +# +# for x in A: +# counts[x] += 1 +# +# duplicate = 0 +# missing = 0 +# +# for i, c in enumerate(counts): +# if c == 0: +# missing = i +# elif c == 2: +# duplicate = i +# +# return DuplicateAndMissing(duplicate, missing) + +# time: O(n) +# space: O(1) def find_duplicate_missing(A: List[int]) -> DuplicateAndMissing: - # TODO - you fill in here. - return DuplicateAndMissing(0, 0) + dup_xor_miss = 0 + for i, x in enumerate(A): + dup_xor_miss ^= x + dup_xor_miss ^= i + + one_result = 0 + differing_bit = dup_xor_miss ^ (dup_xor_miss & (dup_xor_miss - 1)) + + for i, x in enumerate(A): + if i & differing_bit: + one_result ^= i + if x & differing_bit: + one_result ^= x + + other_result = dup_xor_miss ^ one_result + duplicate, missing = (one_result, other_result) if one_result in A else (other_result, one_result) + + return DuplicateAndMissing(duplicate, missing) def res_printer(prop, value): diff --git a/epi_judge_python/search_row_col_sorted_matrix.py b/epi_judge_python/search_row_col_sorted_matrix.py index 2e39e121e..aff08ed6c 100644 --- a/epi_judge_python/search_row_col_sorted_matrix.py +++ b/epi_judge_python/search_row_col_sorted_matrix.py @@ -3,9 +3,21 @@ from test_framework import generic_test +# time: O(m + n), m = no. of rows, n = no. of cols +# space: O(1) def matrix_search(A: List[List[int]], x: int) -> bool: - # TODO - you fill in here. - return True + col = len(A[0]) - 1 + row = 0 + + while col >= 0 and row < len(A): + if A[row][col] == x: + return True + elif A[row][col] < x: + row += 1 + else: + col -= 1 + + return False if __name__ == '__main__': diff --git a/epi_judge_python/search_shifted_sorted_array.py b/epi_judge_python/search_shifted_sorted_array.py index 127084897..2b504b45e 100644 --- a/epi_judge_python/search_shifted_sorted_array.py +++ b/epi_judge_python/search_shifted_sorted_array.py @@ -3,9 +3,21 @@ from test_framework import generic_test +# time: O(log n) +# space: O(1) def search_smallest(A: List[int]) -> int: - # TODO - you fill in here. - return 0 + lower = 0 + upper = len(A) - 1 + + while lower < upper: + mid = lower + (upper - lower) // 2 + + if A[mid] > A[upper]: + lower = mid + 1 + else: + upper = mid + + return lower if __name__ == '__main__': diff --git a/epi_judge_python/smallest_subarray_covering_all_values.py b/epi_judge_python/smallest_subarray_covering_all_values.py index b7dc2a595..92ebdd229 100644 --- a/epi_judge_python/smallest_subarray_covering_all_values.py +++ b/epi_judge_python/smallest_subarray_covering_all_values.py @@ -1,6 +1,6 @@ import collections import functools -from typing import List +from typing import Dict, List from test_framework import generic_test from test_framework.test_utils import enable_executor_hook @@ -8,11 +8,28 @@ Subarray = collections.namedtuple('Subarray', ('start', 'end')) +# time: O(n) +# space: O(k), where k is the number of keywords def find_smallest_sequentially_covering_subset(paragraph: List[str], keywords: List[str] ) -> Subarray: - # TODO - you fill in here. - return Subarray(0, 0) + result = Subarray(0, len(paragraph) - 1) + last_occurence: Dict[str, int] = dict() + start: Dict[int, int] = dict() + + for i, word in enumerate(paragraph): + if word == keywords[0]: + start[i] = i + last_occurence[word] = i + elif word in keywords: + prev_keyword = keywords[keywords.index(word) - 1] + if prev_keyword in last_occurence: + start[i] = start[last_occurence[prev_keyword]] + last_occurence[word] = i + if word == keywords[-1] and i - start[i] < result.end - result.start: + result = Subarray(start[i], i) + + return result @enable_executor_hook diff --git a/epi_judge_python/smallest_subarray_covering_set.py b/epi_judge_python/smallest_subarray_covering_set.py index d67363c56..95ae7bdeb 100644 --- a/epi_judge_python/smallest_subarray_covering_set.py +++ b/epi_judge_python/smallest_subarray_covering_set.py @@ -9,10 +9,55 @@ Subarray = collections.namedtuple('Subarray', ('start', 'end')) +# time: O(n^2) +# space: O(k), where k is the number of keywords +# def find_smallest_subarray_covering_set(paragraph: List[str], +# keywords: Set[str]) -> Subarray: +# covered: Set[str] = set() +# l_res = 0 +# r_res = len(paragraph) - 1 +# +# l = r = 0 +# while l <= r < len(paragraph): +# covered = set( +# filter(lambda w: w in keywords, paragraph[l:r+1])) +# if len(covered) < len(keywords): +# r += 1 +# else: +# if (r - l) < (r_res - l_res): +# l_res = l +# r_res = r +# l += 1 +# +# return Subarray(l_res, r_res) + + +# time: O(n) +# space: O(k), where k is the number of keywords def find_smallest_subarray_covering_set(paragraph: List[str], keywords: Set[str]) -> Subarray: - # TODO - you fill in here. - return Subarray(0, 0) + keywords_to_cover = collections.Counter(keywords) + result = Subarray(start=-1, end=-1) + remaining_to_cover = len(keywords) + left = 0 + for right, p in enumerate(paragraph): + if p in keywords: + keywords_to_cover[p] -= 1 + if keywords_to_cover[p] == 0: + remaining_to_cover -= 1 + + while remaining_to_cover == 0: + if result == Subarray(start=-1, end=-1) or ( + right - left < result.end - result.start): + result = Subarray(start=left, end=right) + + pl = paragraph[left] + if pl in keywords: + keywords_to_cover[pl] += 1 + if keywords_to_cover[pl] > 0: + remaining_to_cover += 1 + left += 1 + return result @enable_executor_hook diff --git a/epi_judge_python/snake_string.py b/epi_judge_python/snake_string.py index e367f2aed..486777dd1 100644 --- a/epi_judge_python/snake_string.py +++ b/epi_judge_python/snake_string.py @@ -1,12 +1,29 @@ from test_framework import generic_test +import math +# time: O(n) +# space: O(n) +# def snake_string(s: str) -> str: +# snake = [] +# for i in range(1, len(s), 4): +# snake.append(s[i]) +# for i in range(0, len(s), 2): +# snake.append(s[i]) +# for i in range(3, len(s), 4): +# snake.append(s[i]) +# return "".join(snake) + + +# time: O(n) +# space: O(n) def snake_string(s: str) -> str: - # TODO - you fill in here. - return '' + return s[1::4] + s[::2] + s[3::4] -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('snake_string.py', 'snake_string.tsv', - snake_string)) + generic_test.generic_test_main( + "snake_string.py", "snake_string.tsv", snake_string + ) + ) diff --git a/epi_judge_python/sort_almost_sorted_array.py b/epi_judge_python/sort_almost_sorted_array.py index 114a59272..baa850812 100644 --- a/epi_judge_python/sort_almost_sorted_array.py +++ b/epi_judge_python/sort_almost_sorted_array.py @@ -1,12 +1,26 @@ -from typing import Iterator, List +from typing import Iterator, List, Set +import heapq from test_framework import generic_test +# time: O(n log k) +# space: O(k), (space for result not counted) def sort_approximately_sorted_array(sequence: Iterator[int], k: int) -> List[int]: - # TODO - you fill in here. - return [] + result = [] + min_heap = [] + + for _ in range(k): + heapq.heappush(min_heap, next(sequence)) + + for x in sequence: + result.append(heapq.heappushpop(min_heap, x)) + + while min_heap: + result.append(heapq.heappop(min_heap)) + + return result def sort_approximately_sorted_array_wrapper(sequence, k): diff --git a/epi_judge_python/sort_increasing_decreasing_array.py b/epi_judge_python/sort_increasing_decreasing_array.py index 820d433bf..2bbe9df8d 100644 --- a/epi_judge_python/sort_increasing_decreasing_array.py +++ b/epi_judge_python/sort_increasing_decreasing_array.py @@ -1,12 +1,35 @@ from typing import List +import heapq from test_framework import generic_test +# time: O(n log k), where k is the number of lists +# space: O(n) def sort_k_increasing_decreasing_array(A: List[int]) -> List[int]: - # TODO - you fill in here. - return [] + was_increasing = True + sorted_lists = [[A[0]]] + for i in range(1, len(A)): + is_increasing = A[i-1] <= A[i] + if was_increasing == is_increasing: + sorted_lists[-1].append(A[i]) + else: + if not was_increasing: + sorted_lists[-1] = list(reversed(sorted_lists[-1])) + sorted_lists.append([A[i]]) + was_increasing = is_increasing + + if not was_increasing: + sorted_lists[-1] = list(reversed(sorted_lists[-1])) + + return list(heapq.merge(*sorted_lists)) + + +# time: O(n log n), but it's faster :') +# is n too small or k/n too large? +# def sort_k_increasing_decreasing_array(A: List[int]) -> List[int]: +# return sorted(A) if __name__ == '__main__': exit( diff --git a/epi_judge_python/sorted_array_remove_dups.py b/epi_judge_python/sorted_array_remove_dups.py index 7dee495ba..5c84b9757 100644 --- a/epi_judge_python/sorted_array_remove_dups.py +++ b/epi_judge_python/sorted_array_remove_dups.py @@ -4,11 +4,50 @@ from test_framework import generic_test from test_framework.test_utils import enable_executor_hook +# shift everything after a duplicte to the left by one +# time: O(n^2) +# space: O(1) +""" +def delete_duplicates(A: List[int]) -> int: + num_del = 0 + i = 0 + while i + num_del < len(A) - 1: + if A[i] == A[i + 1]: + num_del += 1 + for j in range(i, len(A) - num_del): + A[j] = A[j + 1] + else: + i += 1 + return len(A) - num_del +""" + +# using a dict +# time: O(n) +# space: O(n) +""" +def delete_duplicates(A: List[int]) -> int: + res = [] + occurred = {} + for i in range(len(A)): + if not A[i] in occurred: + res.append(A[i]) + occurred[A[i]] = True + A[:] = res + return len(res) +""" -# Returns the number of valid entries after deletion. +# reduce number of shifts +# time: O(n) +# space: O(1) def delete_duplicates(A: List[int]) -> int: - # TODO - you fill in here. - return 0 + if (len(A) == 0): + return 0 + write = 1 + for i in range(len(A)): + if A[i] != A[write - 1]: + A[write] = A[i] + write += 1 + return write @enable_executor_hook diff --git a/epi_judge_python/sorted_arrays_merge.py b/epi_judge_python/sorted_arrays_merge.py index d8d32e698..8c3cee230 100644 --- a/epi_judge_python/sorted_arrays_merge.py +++ b/epi_judge_python/sorted_arrays_merge.py @@ -1,13 +1,72 @@ -from typing import List +from typing import List, Tuple +from itertools import chain +import heapq from test_framework import generic_test +# time: O(n log n) +# space: O(n) def merge_sorted_arrays(sorted_arrays: List[List[int]]) -> List[int]: - # TODO - you fill in here. - return [] + return sorted(chain(*sorted_arrays)) +# time: O(n log k), where k is the number of lists +# space: O(k), (+ O(n) for result) +# class Node: +# def __init__(self, list_i, i, value): +# self.list_i = list_i +# self.i = i +# self.value = value +# +# def __lt__(self, other): +# return self.value < other.value +# +# def merge_sorted_arrays(sorted_arrays: List[List[int]]) -> List[int]: +# min_heap: List[Node] = [] +# result = [] +# +# for i in range(len(sorted_arrays)): +# heapq.heappush(min_heap, Node(i, 0, sorted_arrays[i][0])) +# +# while len(min_heap) > 0: +# next = heapq.heappop(min_heap) +# result.append(next.value) +# if len(sorted_arrays[next.list_i]) > next.i + 1: +# heapq.heappush(min_heap, Node(next.list_i, next.i + 1, +# sorted_arrays[next.list_i][next.i + 1])) +# +# return result + + +# time: O(n log k), where k is the number of lists +# space: O(k), (+ O(n) for result) +# def merge_sorted_arrays(sorted_arrays: List[List[int]]) -> List[int]: +# min_heap: List[Tuple[int, int]] = [] +# sorted_array_iters = [iter(x) for x in sorted_arrays] +# +# for i, it in enumerate(sorted_array_iters): +# first_element = next(it, None) +# if first_element is not None: +# heapq.heappush(min_heap, (first_element, i)) +# +# result = [] +# +# while min_heap: +# smallest_entry, smallest_array_i = heapq.heappop(min_heap) +# smallest_array_iter = sorted_array_iters[smallest_array_i] +# result.append(smallest_entry) +# next_element = next(smallest_array_iter, None) +# if next_element is not None: +# heapq.heappush(min_heap, (next_element, smallest_array_i)) +# +# return result + + +# pythonic solution +# def merge_sorted_arrays(sorted_arrays: List[List[int]]) -> List[int]: +# return list(heapq.merge(*sorted_arrays)) + if __name__ == '__main__': exit( generic_test.generic_test_main('sorted_arrays_merge.py', diff --git a/epi_judge_python/sorted_lists_merge.py b/epi_judge_python/sorted_lists_merge.py index 181386fdd..52b8c0893 100644 --- a/epi_judge_python/sorted_lists_merge.py +++ b/epi_judge_python/sorted_lists_merge.py @@ -4,14 +4,27 @@ from test_framework import generic_test -def merge_two_sorted_lists(L1: Optional[ListNode], - L2: Optional[ListNode]) -> Optional[ListNode]: - # TODO - you fill in here. - return None +# time: O(n) +# space: O(1) +def merge_two_sorted_lists( + L1: Optional[ListNode], L2: Optional[ListNode] +) -> Optional[ListNode]: + dummy_head = tail = ListNode() + while L1 and L2: + if L1.data < L2.data: + tail.next = L1 + L1 = L1.next + else: + tail.next = L2 + L2 = L2.next + tail = tail.next + tail.next = L1 or L2 + return dummy_head.next -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('sorted_lists_merge.py', - 'sorted_lists_merge.tsv', - merge_two_sorted_lists)) + generic_test.generic_test_main( + "sorted_lists_merge.py", "sorted_lists_merge.tsv", merge_two_sorted_lists + ) + ) diff --git a/epi_judge_python/spiral_ordering.py b/epi_judge_python/spiral_ordering.py index 857b37fd3..c28309b6d 100644 --- a/epi_judge_python/spiral_ordering.py +++ b/epi_judge_python/spiral_ordering.py @@ -3,13 +3,29 @@ from test_framework import generic_test +# time: O(n), where n is the total number of elements in the matrix +# space: O(n) def matrix_in_spiral_order(square_matrix: List[List[int]]) -> List[int]: - # TODO - you fill in here. - return [] + result = [] + size = len(square_matrix) + offset = 0 + while size > 1: + result += square_matrix[offset][offset : offset + size - 1] + for i in range(offset, offset + size - 1): + result.append(square_matrix[i][offset + size - 1]) + result += square_matrix[offset + size - 1][offset + 1 : offset + size][::-1] + for i in range(offset + size - 1, offset, -1): + result.append(square_matrix[i][offset]) + size -= 2 + offset += 1 + if size == 1: + result.append(square_matrix[offset][offset]) + return result -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('spiral_ordering.py', - 'spiral_ordering.tsv', - matrix_in_spiral_order)) + generic_test.generic_test_main( + "spiral_ordering.py", "spiral_ordering.tsv", matrix_in_spiral_order + ) + ) diff --git a/epi_judge_python/spreadsheet_encoding.py b/epi_judge_python/spreadsheet_encoding.py index 02c3558c5..ccac5b6b7 100644 --- a/epi_judge_python/spreadsheet_encoding.py +++ b/epi_judge_python/spreadsheet_encoding.py @@ -1,13 +1,27 @@ from test_framework import generic_test +import functools +# for loop +# time: O(n) +# space: O(1) +# def ss_decode_col_id(col: str) -> int: +# result = 0 +# for c in col: +# result = result * 26 + ord(c) - ord("A") + 1 +# return result + + +# reduce function +# time: O(n) +# space: O(1) def ss_decode_col_id(col: str) -> int: - # TODO - you fill in here. - return 0 + return functools.reduce(lambda acc, cur: acc * 26 + ord(cur) - ord("A") + 1, col, 0) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('spreadsheet_encoding.py', - 'spreadsheet_encoding.tsv', - ss_decode_col_id)) + generic_test.generic_test_main( + "spreadsheet_encoding.py", "spreadsheet_encoding.tsv", ss_decode_col_id + ) + ) diff --git a/epi_judge_python/stack_with_max.py b/epi_judge_python/stack_with_max.py index eedd2e3f2..3ae6f5bab 100644 --- a/epi_judge_python/stack_with_max.py +++ b/epi_judge_python/stack_with_max.py @@ -1,23 +1,59 @@ from test_framework import generic_test from test_framework.test_failure import TestFailure +from collections import namedtuple +from typing import List +# keep track of max value +# all methods are O(1), except pop() if the max element is removed +# additional space is O(1) +# class Stack: +# entries = [] +# max_entry = float("-inf") + +# def empty(self) -> bool: +# return len(self.entries) == 0 + +# def max(self) -> int: +# return self.max_entry + +# def pop(self) -> int: +# popped = self.entries.pop() +# if popped == self.max_entry: +# self.max_entry = self.find_max() +# return popped + +# def push(self, x: int) -> None: +# if x > self.max_entry: +# self.max_entry = x +# self.entries.append(x) + +# def find_max(self) -> int: +# max = float("-inf") +# for entry in self.entries: +# if entry > max: +# max = entry +# return max + + +# keep track of max value to the left of each element +# all methods are O(1) +# additional space is O(n) class Stack: + Entry = namedtuple("Entry", ["value", "max"]) + entries: List[Entry] = [] + def empty(self) -> bool: - # TODO - you fill in here. - return True + return len(self.entries) == 0 def max(self) -> int: - # TODO - you fill in here. - return 0 + return self.entries[-1].max def pop(self) -> int: - # TODO - you fill in here. - return 0 + return self.entries.pop().value def push(self, x: int) -> None: - # TODO - you fill in here. - return + self.entries.append(self.Entry(x, x if self.empty() else max(x, self.max()))) def stack_tester(ops): @@ -25,32 +61,37 @@ def stack_tester(ops): s = Stack() for (op, arg) in ops: - if op == 'Stack': + if op == "Stack": s = Stack() - elif op == 'push': + elif op == "push": s.push(arg) - elif op == 'pop': + elif op == "pop": result = s.pop() if result != arg: - raise TestFailure('Pop: expected ' + str(arg) + ', got ' + - str(result)) - elif op == 'max': + raise TestFailure( + "Pop: expected " + str(arg) + ", got " + str(result) + ) + elif op == "max": result = s.max() if result != arg: - raise TestFailure('Max: expected ' + str(arg) + ', got ' + - str(result)) - elif op == 'empty': + raise TestFailure( + "Max: expected " + str(arg) + ", got " + str(result) + ) + elif op == "empty": result = int(s.empty()) if result != arg: - raise TestFailure('Empty: expected ' + str(arg) + - ', got ' + str(result)) + raise TestFailure( + "Empty: expected " + str(arg) + ", got " + str(result) + ) else: - raise RuntimeError('Unsupported stack operation: ' + op) + raise RuntimeError("Unsupported stack operation: " + op) except IndexError: - raise TestFailure('Unexpected IndexError exception') + raise TestFailure("Unexpected IndexError exception") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('stack_with_max.py', - 'stack_with_max.tsv', stack_tester)) + generic_test.generic_test_main( + "stack_with_max.py", "stack_with_max.tsv", stack_tester + ) + ) diff --git a/epi_judge_python/string_decompositions_into_dictionary_words.py b/epi_judge_python/string_decompositions_into_dictionary_words.py index 26358d6ef..fb026677a 100644 --- a/epi_judge_python/string_decompositions_into_dictionary_words.py +++ b/epi_judge_python/string_decompositions_into_dictionary_words.py @@ -1,12 +1,81 @@ -from typing import List +from typing import Counter, List from test_framework import generic_test +# time: O(n^2) +# space: O(n) +# def find_all_substrings(s: str, words: List[str]) -> List[int]: +# result = [] +# word_size = len(words[0]) +# +# for i in range(len(s)): +# word_count = Counter(words) +# for j in range(i, len(s), word_size): +# word = s[j:j+word_size] +# if word in word_count: +# word_count[word] -= 1 +# if word_count[word] == 0: +# del word_count[word] +# else: +# break +# if not word_count: +# result.append(i) +# +# return result + +# def find_all_substrings(s: str, words: List[str]) -> List[int]: +# result = [] +# word_size = len(words[0]) +# +# for offset in range(word_size): +# word_count = Counter(words) +# start = end = offset +# while start <= end < len(s): +# word = s[end:end+word_size] +# if word in word_count: +# word_count[word] -= 1 +# if word_count[word] == 0: +# del word_count[word] +# if not word_count: +# result.append(start) +# word_count.update([s[start:start+word_size]]) +# start += word_size +# end += word_size +# else: +# if start == end: +# start += word_size +# end += word_size +# word_count = Counter(words) +# else: +# word_count.update([s[start:start+word_size]]) +# start += word_size +# +# return sorted(result) + + +# time: O(Nnm), where N is the length of s, n is the length of each word and +# m is the number of words +# space: O(n) def find_all_substrings(s: str, words: List[str]) -> List[int]: - # TODO - you fill in here. - return [] + def match_all_words_in_dict(start): + curr_string_to_freq = Counter() + for i in range(start, start + len(words) * unit_size, unit_size): + curr_word = s[i:i+unit_size] + it = word_to_freq[curr_word] + if it == 0: + return False + curr_string_to_freq[curr_word] += 1 + if curr_string_to_freq[curr_word] > it: + return False + return True + word_to_freq = Counter(words) + unit_size = len(words[0]) + return [ + i for i in range(len(s) - unit_size * len(words) + 1) + if match_all_words_in_dict(i) + ] if __name__ == '__main__': exit( diff --git a/epi_judge_python/string_integer_interconversion.py b/epi_judge_python/string_integer_interconversion.py index 0c5e76ef9..1d7aa4f88 100644 --- a/epi_judge_python/string_integer_interconversion.py +++ b/epi_judge_python/string_integer_interconversion.py @@ -2,25 +2,44 @@ from test_framework.test_failure import TestFailure +# time: O(n) +# space: O(n) def int_to_string(x: int) -> str: - # TODO - you fill in here. - return '0' - - + if x == 0: + return "0" + isNegative = x < 0 + x = abs(x) + string = [] + while x != 0: + string.append(chr(ord("0") + x % 10)) + x //= 10 + if isNegative: + string.append("-") + return "".join(reversed(string)) + + +# time: O(n) +# space: O(n) def string_to_int(s: str) -> int: - # TODO - you fill in here. - return 0 + integer = 0 + sign = -1 if s[0] == "-" else 1 + for i in range(s[0] in "+-", len(s)): + integer = 10 * integer + ord(s[i]) - ord("0") + return sign * integer def wrapper(x, s): if int(int_to_string(x)) != x: - raise TestFailure('Int to string conversion failed') + raise TestFailure("Int to string conversion failed") if string_to_int(s) != x: - raise TestFailure('String to int conversion failed') + raise TestFailure("String to int conversion failed") -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('string_integer_interconversion.py', - 'string_integer_interconversion.tsv', - wrapper)) + generic_test.generic_test_main( + "string_integer_interconversion.py", + "string_integer_interconversion.tsv", + wrapper, + ) + ) diff --git a/epi_judge_python/substring_match.py b/epi_judge_python/substring_match.py index 5d97a5761..35b388601 100644 --- a/epi_judge_python/substring_match.py +++ b/epi_judge_python/substring_match.py @@ -1,12 +1,49 @@ from test_framework import generic_test +import functools +# time: O(n^2) +# space: O(1) +# def rabin_karp(t: str, s: str) -> int: +# for i in range(len(t) - len(s) + 1): +# matchStart = i +# matchLen = 0 +# while ( +# i + matchLen < len(t) +# and matchLen < len(s) +# and t[i + matchLen] == s[matchLen] +# ): +# matchLen += 1 +# if matchLen == len(s): +# return matchStart +# return -1 + +# time: O(n+m), n is length of t, m is length of s +# space: O(1) def rabin_karp(t: str, s: str) -> int: - # TODO - you fill in here. - return 0 + if len(s) > len(t): + return -1 + BASE = 26 + + def rolling_hash(hash: int, char: str) -> int: + return hash * BASE + ord(char) + + t_hash = functools.reduce(rolling_hash, t[: len(s)], 0) + s_hash = functools.reduce(rolling_hash, s, 0) + + for i in range(len(s), len(t) + 1): + if t_hash == s_hash and t[i - len(s) : i] == s: + return i - len(s) + if i == len(t): + return -1 + t_hash -= ord(t[i - len(s)]) * BASE ** (len(s) - 1) + t_hash = t_hash * BASE + ord(t[i]) + return -1 -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('substring_match.py', - 'substring_match.tsv', rabin_karp)) + generic_test.generic_test_main( + "substring_match.py", "substring_match.tsv", rabin_karp + ) + ) diff --git a/epi_judge_python/successor_in_tree.py b/epi_judge_python/successor_in_tree.py index 6d7379c55..fd4fec9d8 100644 --- a/epi_judge_python/successor_in_tree.py +++ b/epi_judge_python/successor_in_tree.py @@ -7,9 +7,17 @@ from test_framework.test_utils import enable_executor_hook +# time: O(h) +# space: O(1) def find_successor(node: BinaryTreeNode) -> Optional[BinaryTreeNode]: - # TODO - you fill in here. - return None + if node.right: + node = node.right + while node.left: + node = node.left + return node + while node.parent and node == node.parent.right: + node = node.parent + return node.parent @enable_executor_hook @@ -21,8 +29,9 @@ def find_successor_wrapper(executor, tree, node_idx): return result.data if result else -1 -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('successor_in_tree.py', - 'successor_in_tree.tsv', - find_successor_wrapper)) + generic_test.generic_test_main( + "successor_in_tree.py", "successor_in_tree.tsv", find_successor_wrapper + ) + ) diff --git a/epi_judge_python/sum_root_to_leaf.py b/epi_judge_python/sum_root_to_leaf.py index da54ccca5..02534d298 100644 --- a/epi_judge_python/sum_root_to_leaf.py +++ b/epi_judge_python/sum_root_to_leaf.py @@ -2,13 +2,24 @@ from test_framework import generic_test +# time: O(n) +# space: O(h) def sum_root_to_leaf(tree: BinaryTreeNode) -> int: - # TODO - you fill in here. - return 0 + def traverse(tree: BinaryTreeNode, number: int) -> int: + if not tree: + return 0 + new_number = number * 2 + tree.data + if tree.left or tree.right: + return traverse(tree.left, new_number) + traverse(tree.right, new_number) + return new_number -if __name__ == '__main__': + return traverse(tree, 0) + + +if __name__ == "__main__": exit( - generic_test.generic_test_main('sum_root_to_leaf.py', - 'sum_root_to_leaf.tsv', - sum_root_to_leaf)) + generic_test.generic_test_main( + "sum_root_to_leaf.py", "sum_root_to_leaf.tsv", sum_root_to_leaf + ) + ) diff --git a/epi_judge_python/sunset_view.py b/epi_judge_python/sunset_view.py index ba03fc30a..e28b005d3 100644 --- a/epi_judge_python/sunset_view.py +++ b/epi_judge_python/sunset_view.py @@ -1,18 +1,27 @@ +from collections import namedtuple from typing import Iterator, List from test_framework import generic_test +# time: O(n) def examine_buildings_with_sunset(sequence: Iterator[int]) -> List[int]: - # TODO - you fill in here. - return [] + House = namedtuple("House", ["id", "height"]) + houses_with_view = [] + for id, height in enumerate(sequence): + while houses_with_view and houses_with_view[-1].height <= height: + houses_with_view.pop() + houses_with_view.append(House(id, height)) + return [house.id for house in reversed(houses_with_view)] def examine_buildings_with_sunset_wrapper(sequence): return examine_buildings_with_sunset(iter(sequence)) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('sunset_view.py', 'sunset_view.tsv', - examine_buildings_with_sunset)) + generic_test.generic_test_main( + "sunset_view.py", "sunset_view.tsv", examine_buildings_with_sunset + ) + ) diff --git a/epi_judge_python/swap_bits.py b/epi_judge_python/swap_bits.py index ced0b7ba9..518c2a0ea 100644 --- a/epi_judge_python/swap_bits.py +++ b/epi_judge_python/swap_bits.py @@ -1,10 +1,12 @@ from test_framework import generic_test +# time complexity: O(1) def swap_bits(x, i, j): - # TODO - you fill in here. - return 0 - + if ((x >> i) & 1 != (x >> j) & 1): + # invert both bits if they differ + x ^= (1 << i) | (1 << j) + return x if __name__ == '__main__': exit( diff --git a/epi_judge_python/tree_connect_leaves.py b/epi_judge_python/tree_connect_leaves.py index cdfb68fee..89012c402 100644 --- a/epi_judge_python/tree_connect_leaves.py +++ b/epi_judge_python/tree_connect_leaves.py @@ -7,9 +7,12 @@ from test_framework.test_utils import enable_executor_hook -def create_list_of_leaves(tree: BinaryTreeNode) -> List[BinaryTreeNode]: - # TODO - you fill in here. - return [] +def create_list_of_leaves(tree: BinaryTreeNode | None) -> List[BinaryTreeNode]: + if not tree: + return [] + if not tree.left and not tree.right: + return [tree] + return create_list_of_leaves(tree.left) + create_list_of_leaves(tree.right) @enable_executor_hook @@ -17,12 +20,15 @@ def create_list_of_leaves_wrapper(executor, tree): result = executor.run(functools.partial(create_list_of_leaves, tree)) if any(x is None for x in result): - raise TestFailure('Result list can\'t contain None') + raise TestFailure("Result list can't contain None") return [x.data for x in result] -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_connect_leaves.py', - 'tree_connect_leaves.tsv', - create_list_of_leaves_wrapper)) + generic_test.generic_test_main( + "tree_connect_leaves.py", + "tree_connect_leaves.tsv", + create_list_of_leaves_wrapper, + ) + ) diff --git a/epi_judge_python/tree_exterior.py b/epi_judge_python/tree_exterior.py index 4716adb3d..65cd74aff 100644 --- a/epi_judge_python/tree_exterior.py +++ b/epi_judge_python/tree_exterior.py @@ -7,14 +7,41 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n) +# space: O(h) def exterior_binary_tree(tree: BinaryTreeNode) -> List[BinaryTreeNode]: - # TODO - you fill in here. - return [] + def get_leaves(tree: BinaryTreeNode | None) -> List[BinaryTreeNode]: + if not tree: + return [] + if not tree.left and not tree.right: + return [tree] + return get_leaves(tree.left) + get_leaves(tree.right) + + if not tree: + return [] + + exterior = [tree] + + cur = tree.left + while cur and (cur.left or cur.right): + exterior.append(cur) + cur = cur.left or cur.right + + exterior += get_leaves(tree.left) + get_leaves(tree.right) + + right = [] + cur = tree.right + while cur and (cur.right or cur.left): + right.append(cur) + cur = cur.right or cur.left + exterior += list(reversed(right)) + + return exterior def create_output_list(L): if any(l is None for l in L): - raise TestFailure('Resulting list contains None') + raise TestFailure("Resulting list contains None") return [l.data for l in L] @@ -25,7 +52,9 @@ def create_output_list_wrapper(executor, tree): return create_output_list(result) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_exterior.py', 'tree_exterior.tsv', - create_output_list_wrapper)) + generic_test.generic_test_main( + "tree_exterior.py", "tree_exterior.tsv", create_output_list_wrapper + ) + ) diff --git a/epi_judge_python/tree_from_preorder_inorder.py b/epi_judge_python/tree_from_preorder_inorder.py index 07c5701ec..d84041d80 100644 --- a/epi_judge_python/tree_from_preorder_inorder.py +++ b/epi_judge_python/tree_from_preorder_inorder.py @@ -4,14 +4,55 @@ from test_framework import generic_test -def binary_tree_from_preorder_inorder(preorder: List[int], - inorder: List[int]) -> BinaryTreeNode: - # TODO - you fill in here. - return BinaryTreeNode() +# time: O(n^2) ? +# space: O(nh) +# def binary_tree_from_preorder_inorder( +# preorder: List[int], inorder: List[int] +# ) -> BinaryTreeNode | None: +# if not preorder: +# return None +# root_idx = inorder.index(preorder[0]) +# return BinaryTreeNode( +# preorder[0], +# binary_tree_from_preorder_inorder( +# preorder[1 : root_idx + 1], inorder[0:root_idx] +# ), +# binary_tree_from_preorder_inorder( +# preorder[root_idx + 1 :], inorder[root_idx + 1 :] +# ), +# ) -if __name__ == '__main__': + +# time: O(n) +# space: O(n) +def binary_tree_from_preorder_inorder( + preorder: List[int], inorder: List[int] +) -> BinaryTreeNode | None: + node_to_inorder_idx = {data: i for i, data in enumerate(inorder)} + + def get_tree(pre_start, pre_end, in_start, in_end): + if pre_start >= pre_end or in_start >= in_end: + return None + + root_idx = node_to_inorder_idx[preorder[pre_start]] + left_subtree_size = root_idx - in_start + return BinaryTreeNode( + preorder[pre_start], + get_tree( + pre_start + 1, pre_start + left_subtree_size + 1, in_start, root_idx + ), + get_tree(pre_start + left_subtree_size + 1, pre_end, root_idx + 1, in_end), + ) + + return get_tree(0, len(preorder), 0, len(inorder)) + + +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_from_preorder_inorder.py', - 'tree_from_preorder_inorder.tsv', - binary_tree_from_preorder_inorder)) + generic_test.generic_test_main( + "tree_from_preorder_inorder.py", + "tree_from_preorder_inorder.tsv", + binary_tree_from_preorder_inorder, + ) + ) diff --git a/epi_judge_python/tree_from_preorder_with_null.py b/epi_judge_python/tree_from_preorder_with_null.py index 649cf9d45..6375661b2 100644 --- a/epi_judge_python/tree_from_preorder_with_null.py +++ b/epi_judge_python/tree_from_preorder_with_null.py @@ -6,19 +6,33 @@ from test_framework.test_utils import enable_executor_hook +# time: O(n) +# space: O(1) def reconstruct_preorder(preorder: List[int]) -> BinaryTreeNode: - # TODO - you fill in here. - return BinaryTreeNode() + def reconstruct_preorder_helper(preorder_iter): + data = next(preorder_iter) + if data == None: + return None + return BinaryTreeNode( + data, + reconstruct_preorder_helper(preorder_iter), + reconstruct_preorder_helper(preorder_iter), + ) + + return reconstruct_preorder_helper(iter(preorder)) @enable_executor_hook def reconstruct_preorder_wrapper(executor, data): - data = [None if x == 'null' else int(x) for x in data] + data = [None if x == "null" else int(x) for x in data] return executor.run(functools.partial(reconstruct_preorder, data)) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_from_preorder_with_null.py', - 'tree_from_preorder_with_null.tsv', - reconstruct_preorder_wrapper)) + generic_test.generic_test_main( + "tree_from_preorder_with_null.py", + "tree_from_preorder_with_null.tsv", + reconstruct_preorder_wrapper, + ) + ) diff --git a/epi_judge_python/tree_inorder.py b/epi_judge_python/tree_inorder.py index a4b080ec7..e4f690b10 100644 --- a/epi_judge_python/tree_inorder.py +++ b/epi_judge_python/tree_inorder.py @@ -1,15 +1,49 @@ -from typing import List +from typing import List, Tuple from binary_tree_node import BinaryTreeNode from test_framework import generic_test +# time: O(n) +# space: O(h) +# def inorder_traversal(tree: BinaryTreeNode) -> List[int]: +# stack = [tree] if tree else [] +# result = [] + +# while stack: +# cur = stack.pop() +# if cur.right: +# stack.append(cur.right) +# if cur.left or cur.right: +# stack.append(BinaryTreeNode(cur.data, None, None)) +# else: +# result.append(cur.data) +# if cur.left: +# stack.append(cur.left) +# return result + + +# time: O(n) +# space: O(h) def inorder_traversal(tree: BinaryTreeNode) -> List[int]: - # TODO - you fill in here. - return [] + stack: List[Tuple[BinaryTreeNode | None, bool]] = [(tree, False)] + result = [] + + while stack: + cur, left_subtree_traversed = stack.pop() + if cur: + if left_subtree_traversed: + result.append(cur.data) + else: + stack.append((cur.right, False)) + stack.append((cur, True)) + stack.append((cur.left, False)) + return result -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_inorder.py', 'tree_inorder.tsv', - inorder_traversal)) + generic_test.generic_test_main( + "tree_inorder.py", "tree_inorder.tsv", inorder_traversal + ) + ) diff --git a/epi_judge_python/tree_level_order.py b/epi_judge_python/tree_level_order.py index 752836ca3..38c9c7427 100644 --- a/epi_judge_python/tree_level_order.py +++ b/epi_judge_python/tree_level_order.py @@ -4,13 +4,43 @@ from test_framework import generic_test +# recursive +# time: O(n) +# def binary_tree_depth_order(tree: BinaryTreeNode) -> List[List[int]]: +# result = [] + +# def traverse(tree: BinaryTreeNode | None, level: int): +# if not tree: +# return +# if len(result) <= level: +# result.append([]) +# result[level].append(tree.data) +# traverse(tree.left, level + 1) +# traverse(tree.right, level + 1) + +# traverse(tree, 0) +# return result + +# iterative with queue +# time: O(n) def binary_tree_depth_order(tree: BinaryTreeNode) -> List[List[int]]: - # TODO - you fill in here. - return [] + result = [] + current_lvl_nodes = [tree] if tree else [] + while current_lvl_nodes: + result.append([node.data for node in current_lvl_nodes]) + nodes = current_lvl_nodes + current_lvl_nodes = [] + for n in nodes: + if n.left: + current_lvl_nodes.append(n.left) + if n.right: + current_lvl_nodes.append(n.right) + return result -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_level_order.py', - 'tree_level_order.tsv', - binary_tree_depth_order)) + generic_test.generic_test_main( + "tree_level_order.py", "tree_level_order.tsv", binary_tree_depth_order + ) + ) diff --git a/epi_judge_python/tree_right_sibling.py b/epi_judge_python/tree_right_sibling.py index 24aefd6af..4fc20c28d 100644 --- a/epi_judge_python/tree_right_sibling.py +++ b/epi_judge_python/tree_right_sibling.py @@ -12,9 +12,31 @@ def __init__(self, data=None): self.next = None # Populates this field. -def construct_right_sibling(tree: BinaryTreeNode) -> None: - # TODO - you fill in here. - return +# time: O(n) +# space: O(h) +# def construct_right_sibling(tree: BinaryTreeNode | None) -> None: +# if not tree: +# return +# cur_left = tree.left +# cur_right = tree.right +# while cur_left and cur_right: +# cur_left.next = cur_right +# cur_left = cur_left.right +# cur_right = cur_right.left +# construct_right_sibling(tree.left) +# construct_right_sibling(tree.right) + +# time: O(n) +# space: O(1) +def construct_right_sibling(tree: BinaryTreeNode | None) -> None: + while tree and tree.left: + cur = tree + while cur: + cur.left.next = cur.right + if cur.next: + cur.right.next = cur.next.left + cur = cur.next + tree = tree.left def traverse_next(node): @@ -35,8 +57,7 @@ def clone_tree(original): if not original: return None cloned = BinaryTreeNode(original.data) - cloned.left, cloned.right = clone_tree(original.left), clone_tree( - original.right) + cloned.left, cloned.right = clone_tree(original.left), clone_tree(original.right) return cloned @@ -46,12 +67,14 @@ def construct_right_sibling_wrapper(executor, tree): executor.run(functools.partial(construct_right_sibling, cloned)) - return [[n.data for n in traverse_next(level)] - for level in traverse_left(cloned)] + return [[n.data for n in traverse_next(level)] for level in traverse_left(cloned)] -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_right_sibling.py', - 'tree_right_sibling.tsv', - construct_right_sibling_wrapper)) + generic_test.generic_test_main( + "tree_right_sibling.py", + "tree_right_sibling.tsv", + construct_right_sibling_wrapper, + ) + ) diff --git a/epi_judge_python/tree_with_parent_inorder.py b/epi_judge_python/tree_with_parent_inorder.py index b79d9fc26..7a4e2c5fc 100644 --- a/epi_judge_python/tree_with_parent_inorder.py +++ b/epi_judge_python/tree_with_parent_inorder.py @@ -4,13 +4,50 @@ from test_framework import generic_test +# time: O(n) +# space: O(1) def inorder_traversal(tree: BinaryTreeNode) -> List[int]: - # TODO - you fill in here. - return [] + result = [] + while tree and tree.left: + tree = tree.left + while tree: + result.append(tree.data) + if tree.right: + tree = tree.right + while tree.left: + tree = tree.left + else: + while tree.parent and tree == tree.parent.right: + tree = tree.parent + tree = tree.parent + return result -if __name__ == '__main__': +# time: O(n) +# space: O(1) +# def inorder_traversal(tree: BinaryTreeNode) -> List[int]: +# prev, result = None, [] +# while tree: +# if prev is tree.parent: +# if tree.left: +# next = tree.left +# else: +# result.append(tree.data) +# next = tree.right or tree.parent +# elif prev is tree.left: +# result.append(tree.data) +# next = tree.right or tree.parent +# else: +# next = tree.parent +# prev, tree = tree, next +# return result + + +if __name__ == "__main__": exit( - generic_test.generic_test_main('tree_with_parent_inorder.py', - 'tree_with_parent_inorder.tsv', - inorder_traversal)) + generic_test.generic_test_main( + "tree_with_parent_inorder.py", + "tree_with_parent_inorder.tsv", + inorder_traversal, + ) + ) diff --git a/epi_judge_python/uniform_random_number.py b/epi_judge_python/uniform_random_number.py index 51b8a79f0..1a61564fd 100644 --- a/epi_judge_python/uniform_random_number.py +++ b/epi_judge_python/uniform_random_number.py @@ -11,9 +11,16 @@ def zero_one_random(): return random.randrange(2) +# generate log2(b-a+1) random bits, repeat if out of bounds +# time: O(log(b-a+1)) def uniform_random(lower_bound: int, upper_bound: int) -> int: - # TODO - you fill in here. - return 0 + nr_outcomes = upper_bound - lower_bound + 1 + while True: + random = 0 + for i in range(nr_outcomes.bit_length()): + random |= zero_one_random() << i + if random < nr_outcomes: + return random + lower_bound @enable_executor_hook diff --git a/epi_judge_python/valid_ip_addresses.py b/epi_judge_python/valid_ip_addresses.py index 216bb3ac4..d54d3ab30 100644 --- a/epi_judge_python/valid_ip_addresses.py +++ b/epi_judge_python/valid_ip_addresses.py @@ -3,18 +3,38 @@ from test_framework import generic_test +# time: O(1), number of valid IP addresses is constant (2^32) +# space: O(1) def get_valid_ip_address(s: str) -> List[str]: - # TODO - you fill in here. - return [] + def is_valid_part(part: str) -> bool: + return len(part) == 1 or (part[0] != "0" and int(part) <= 255) + + valid = [] + parts = [""] * 4 + for a in range(1, min(4, len(s))): + parts[0] = s[:a] + if is_valid_part(parts[0]): + for b in range(a + 1, min(a + 4, len(s))): + parts[1] = s[a:b] + if is_valid_part(parts[1]): + for c in range(b + 1, min(b + 4, len(s))): + parts[2] = s[b:c] + parts[3] = s[c:] + if is_valid_part(parts[2]) and is_valid_part(parts[3]): + valid.append(".".join(parts)) + return valid def comp(a, b): return sorted(a) == sorted(b) -if __name__ == '__main__': +if __name__ == "__main__": exit( - generic_test.generic_test_main('valid_ip_addresses.py', - 'valid_ip_addresses.tsv', - get_valid_ip_address, - comparator=comp)) + generic_test.generic_test_main( + "valid_ip_addresses.py", + "valid_ip_addresses.tsv", + get_valid_ip_address, + comparator=comp, + ) + ) diff --git a/problem_mapping.js b/problem_mapping.js index 867f3ecbc..8206ee055 100644 --- a/problem_mapping.js +++ b/problem_mapping.js @@ -10,7 +10,7 @@ problem_mapping = { "total": 10001 }, "Python: count_bits.py": { - "passed": 0, + "passed": 1, "total": 10001 } }, @@ -24,7 +24,7 @@ problem_mapping = { "total": 10000 }, "Python: parity.py": { - "passed": 0, + "passed": 10000, "total": 10000 } }, @@ -38,7 +38,7 @@ problem_mapping = { "total": 10001 }, "Python: swap_bits.py": { - "passed": 0, + "passed": 10001, "total": 10001 } }, @@ -52,7 +52,7 @@ problem_mapping = { "total": 10000 }, "Python: reverse_bits.py": { - "passed": 0, + "passed": 10000, "total": 10000 } }, @@ -65,8 +65,8 @@ problem_mapping = { "passed": 0, "total": 10000 }, - "Python: closest_int_same_weight.py": { - "passed": 0, + "Python: closest_int_same_weight.py": { + "passed": 10000, "total": 10000 } }, @@ -80,7 +80,7 @@ problem_mapping = { "total": 10006 }, "Python: primitive_multiply.py": { - "passed": 0, + "passed": 10006, "total": 10006 } }, @@ -94,7 +94,7 @@ problem_mapping = { "total": 10000 }, "Python: primitive_divide.py": { - "passed": 0, + "passed": 10000, "total": 10000 } }, @@ -108,7 +108,7 @@ problem_mapping = { "total": 10002 }, "Python: power_x_y.py": { - "passed": 0, + "passed": 10002, "total": 10002 } }, @@ -122,7 +122,7 @@ problem_mapping = { "total": 19952 }, "Python: reverse_digits.py": { - "passed": 0, + "passed": 19952, "total": 19952 } }, @@ -136,7 +136,7 @@ problem_mapping = { "total": 20000 }, "Python: is_number_palindromic.py": { - "passed": 0, + "passed": 20000, "total": 20000 } }, @@ -150,7 +150,7 @@ problem_mapping = { "total": 7 }, "Python: uniform_random_number.py": { - "passed": 0, + "passed": 7, "total": 7 } }, @@ -164,7 +164,7 @@ problem_mapping = { "total": 10000 }, "Python: rectangle_intersection.py": { - "passed": 0, + "passed": 10000, "total": 10000 } } @@ -194,7 +194,7 @@ problem_mapping = { "total": 204 }, "Python: dutch_national_flag.py": { - "passed": 0, + "passed": 204, "total": 204 } }, @@ -208,7 +208,7 @@ problem_mapping = { "total": 505 }, "Python: int_as_array_increment.py": { - "passed": 0, + "passed": 505, "total": 505 } }, @@ -222,7 +222,7 @@ problem_mapping = { "total": 1000 }, "Python: int_as_array_multiply.py": { - "passed": 0, + "passed": 1000, "total": 1000 } }, @@ -236,7 +236,7 @@ problem_mapping = { "total": 2004 }, "Python: advance_by_offsets.py": { - "passed": 0, + "passed": 2004, "total": 2004 } }, @@ -250,7 +250,7 @@ problem_mapping = { "total": 2003 }, "Python: sorted_array_remove_dups.py": { - "passed": 0, + "passed": 2003, "total": 2003 } }, @@ -264,7 +264,7 @@ problem_mapping = { "total": 402 }, "Python: buy_and_sell_stock.py": { - "passed": 0, + "passed": 402, "total": 402 } }, @@ -278,7 +278,7 @@ problem_mapping = { "total": 402 }, "Python: buy_and_sell_stock_twice.py": { - "passed": 0, + "passed": 402, "total": 402 } }, @@ -292,7 +292,7 @@ problem_mapping = { "total": 203 }, "Python: alternating_array.py": { - "passed": 0, + "passed": 203, "total": 203 } }, @@ -306,7 +306,7 @@ problem_mapping = { "total": 24 }, "Python: prime_sieve.py": { - "passed": 0, + "passed": 24, "total": 24 } }, @@ -320,7 +320,7 @@ problem_mapping = { "total": 101 }, "Python: apply_permutation.py": { - "passed": 0, + "passed": 101, "total": 101 } }, @@ -334,7 +334,7 @@ problem_mapping = { "total": 2001 }, "Python: next_permutation.py": { - "passed": 0, + "passed": 2001, "total": 2001 } }, @@ -348,7 +348,7 @@ problem_mapping = { "total": 8 }, "Python: offline_sampling.py": { - "passed": 0, + "passed": 8, "total": 8 } }, @@ -362,7 +362,7 @@ problem_mapping = { "total": 7 }, "Python: online_sampling.py": { - "passed": 0, + "passed": 7, "total": 7 } }, @@ -376,7 +376,7 @@ problem_mapping = { "total": 6 }, "Python: random_permutation.py": { - "passed": 0, + "passed": 6, "total": 6 } }, @@ -390,7 +390,7 @@ problem_mapping = { "total": 9 }, "Python: random_subset.py": { - "passed": 0, + "passed": 9, "total": 9 } }, @@ -404,7 +404,7 @@ problem_mapping = { "total": 10 }, "Python: nonuniform_random_number.py": { - "passed": 0, + "passed": 10, "total": 10 } }, @@ -418,7 +418,7 @@ problem_mapping = { "total": 745 }, "Python: is_valid_sudoku.py": { - "passed": 0, + "passed": 745, "total": 745 } }, @@ -432,7 +432,7 @@ problem_mapping = { "total": 51 }, "Python: spiral_ordering.py": { - "passed": 0, + "passed": 51, "total": 51 } }, @@ -446,7 +446,7 @@ problem_mapping = { "total": 51 }, "Python: matrix_rotation.py": { - "passed": 0, + "passed": 51, "total": 51 } }, @@ -460,7 +460,7 @@ problem_mapping = { "total": 35 }, "Python: pascal_triangle.py": { - "passed": 0, + "passed": 35, "total": 35 } } @@ -490,7 +490,7 @@ problem_mapping = { "total": 15002 }, "Python: string_integer_interconversion.py": { - "passed": 0, + "passed": 15002, "total": 15002 } }, @@ -504,7 +504,7 @@ problem_mapping = { "total": 20001 }, "Python: convert_base.py": { - "passed": 0, + "passed": 20001, "total": 20001 } }, @@ -518,7 +518,7 @@ problem_mapping = { "total": 10000 }, "Python: spreadsheet_encoding.py": { - "passed": 0, + "passed": 10000, "total": 10000 } }, @@ -532,7 +532,7 @@ problem_mapping = { "total": 501 }, "Python: replace_and_remove.py": { - "passed": 0, + "passed": 501, "total": 501 } }, @@ -546,7 +546,7 @@ problem_mapping = { "total": 305 }, "Python: is_string_palindromic_punctuation.py": { - "passed": 0, + "passed": 305, "total": 305 } }, @@ -560,7 +560,7 @@ problem_mapping = { "total": 103 }, "Python: reverse_words.py": { - "passed": 0, + "passed": 103, "total": 103 } }, @@ -574,7 +574,7 @@ problem_mapping = { "total": 40 }, "Python: look_and_say.py": { - "passed": 0, + "passed": 40, "total": 40 } }, @@ -588,7 +588,7 @@ problem_mapping = { "total": 3999 }, "Python: roman_to_integer.py": { - "passed": 0, + "passed": 3999, "total": 3999 } }, @@ -602,7 +602,7 @@ problem_mapping = { "total": 10008 }, "Python: valid_ip_addresses.py": { - "passed": 0, + "passed": 10008, "total": 10008 } }, @@ -616,7 +616,7 @@ problem_mapping = { "total": 500 }, "Python: snake_string.py": { - "passed": 0, + "passed": 500, "total": 500 } }, @@ -630,7 +630,7 @@ problem_mapping = { "total": 200 }, "Python: run_length_compression.py": { - "passed": 0, + "passed": 200, "total": 200 } }, @@ -644,7 +644,7 @@ problem_mapping = { "total": 835 }, "Python: substring_match.py": { - "passed": 0, + "passed": 835, "total": 835 } } @@ -698,7 +698,7 @@ problem_mapping = { "total": 501 }, "Python: sorted_lists_merge.py": { - "passed": 0, + "passed": 501, "total": 501 } }, @@ -712,7 +712,7 @@ problem_mapping = { "total": 210 }, "Python: reverse_sublist.py": { - "passed": 0, + "passed": 210, "total": 210 } }, @@ -726,7 +726,7 @@ problem_mapping = { "total": 102 }, "Python: is_list_cyclic.py": { - "passed": 0, + "passed": 102, "total": 102 } }, @@ -740,7 +740,7 @@ problem_mapping = { "total": 106 }, "Python: do_terminated_lists_overlap.py": { - "passed": 0, + "passed": 106, "total": 106 } }, @@ -754,7 +754,7 @@ problem_mapping = { "total": 120 }, "Python: do_lists_overlap.py": { - "passed": 0, + "passed": 120, "total": 120 } }, @@ -768,7 +768,7 @@ problem_mapping = { "total": 512 }, "Python: delete_node_from_list.py": { - "passed": 0, + "passed": 512, "total": 512 } }, @@ -782,7 +782,7 @@ problem_mapping = { "total": 306 }, "Python: delete_kth_last_from_list.py": { - "passed": 0, + "passed": 306, "total": 306 } }, @@ -796,7 +796,7 @@ problem_mapping = { "total": 203 }, "Python: remove_duplicates_from_sorted_list.py": { - "passed": 0, + "passed": 203, "total": 203 } }, @@ -810,7 +810,7 @@ problem_mapping = { "total": 507 }, "Python: list_cyclic_right_shift.py": { - "passed": 0, + "passed": 507, "total": 507 } }, @@ -824,7 +824,7 @@ problem_mapping = { "total": 1015 }, "Python: even_odd_list_merge.py": { - "passed": 0, + "passed": 1015, "total": 1015 } }, @@ -838,7 +838,7 @@ problem_mapping = { "total": 203 }, "Python: is_list_palindromic.py": { - "passed": 0, + "passed": 203, "total": 203 } }, @@ -852,7 +852,7 @@ problem_mapping = { "total": 208 }, "Python: pivot_list.py": { - "passed": 0, + "passed": 208, "total": 208 } }, @@ -866,7 +866,7 @@ problem_mapping = { "total": 2002 }, "Python: int_as_list_add.py": { - "passed": 0, + "passed": 2002, "total": 2002 } } @@ -882,7 +882,7 @@ problem_mapping = { "total": 101 }, "Python: stack_with_max.py": { - "passed": 0, + "passed": 101, "total": 101 } }, @@ -896,7 +896,7 @@ problem_mapping = { "total": 108 }, "Python: evaluate_rpn.py": { - "passed": 0, + "passed": 108, "total": 108 } }, @@ -910,7 +910,7 @@ problem_mapping = { "total": 78 }, "Python: is_valid_parenthesization.py": { - "passed": 0, + "passed": 78, "total": 78 } }, @@ -924,7 +924,7 @@ problem_mapping = { "total": 255 }, "Python: directory_path_normalization.py": { - "passed": 0, + "passed": 255, "total": 255 } }, @@ -938,7 +938,7 @@ problem_mapping = { "total": 101 }, "Python: sunset_view.py": { - "passed": 0, + "passed": 101, "total": 101 } }, @@ -952,7 +952,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_level_order.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -966,7 +966,7 @@ problem_mapping = { "total": 65 }, "Python: circular_queue.py": { - "passed": 0, + "passed": 65, "total": 65 } }, @@ -980,7 +980,7 @@ problem_mapping = { "total": 65 }, "Python: queue_from_stacks.py": { - "passed": 0, + "passed": 65, "total": 65 } }, @@ -994,7 +994,7 @@ problem_mapping = { "total": 201 }, "Python: queue_with_max.py": { - "passed": 0, + "passed": 201, "total": 201 } } @@ -1010,7 +1010,7 @@ problem_mapping = { "total": 3852 }, "Python: is_tree_balanced.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1024,7 +1024,7 @@ problem_mapping = { "total": 3852 }, "Python: is_tree_symmetric.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1038,7 +1038,7 @@ problem_mapping = { "total": 948 }, "Python: lowest_common_ancestor.py": { - "passed": 0, + "passed": 948, "total": 948 } }, @@ -1052,7 +1052,7 @@ problem_mapping = { "total": 948 }, "Python: lowest_common_ancestor_with_parent.py": { - "passed": 0, + "passed": 948, "total": 948 } }, @@ -1066,7 +1066,7 @@ problem_mapping = { "total": 3849 }, "Python: sum_root_to_leaf.py": { - "passed": 0, + "passed": 3849, "total": 3849 } }, @@ -1080,7 +1080,7 @@ problem_mapping = { "total": 7690 }, "Python: path_sum.py": { - "passed": 0, + "passed": 7690, "total": 7690 } }, @@ -1094,7 +1094,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_inorder.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1108,7 +1108,7 @@ problem_mapping = { "total": 3851 }, "Python: kth_node_in_tree.py": { - "passed": 0, + "passed": 3851, "total": 3851 } }, @@ -1122,7 +1122,7 @@ problem_mapping = { "total": 948 }, "Python: successor_in_tree.py": { - "passed": 0, + "passed": 948, "total": 948 } }, @@ -1136,7 +1136,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_with_parent_inorder.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1150,7 +1150,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_from_preorder_inorder.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1164,7 +1164,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_from_preorder_with_null.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1178,7 +1178,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_connect_leaves.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1192,7 +1192,7 @@ problem_mapping = { "total": 3852 }, "Python: tree_exterior.py": { - "passed": 0, + "passed": 3852, "total": 3852 } }, @@ -1206,7 +1206,7 @@ problem_mapping = { "total": 10 }, "Python: tree_right_sibling.py": { - "passed": 0, + "passed": 10, "total": 10 } } @@ -1222,7 +1222,7 @@ problem_mapping = { "total": 152 }, "Python: sorted_arrays_merge.py": { - "passed": 0, + "passed": 152, "total": 152 } }, @@ -1236,7 +1236,7 @@ problem_mapping = { "total": 203 }, "Python: sort_increasing_decreasing_array.py": { - "passed": 0, + "passed": 203, "total": 203 } }, @@ -1250,7 +1250,7 @@ problem_mapping = { "total": 101 }, "Python: sort_almost_sorted_array.py": { - "passed": 0, + "passed": 101, "total": 101 } }, @@ -1264,7 +1264,7 @@ problem_mapping = { "total": 51 }, "Python: k_closest_stars.py": { - "passed": 0, + "passed": 51, "total": 51 } }, @@ -1278,7 +1278,7 @@ problem_mapping = { "total": 55 }, "Python: online_median.py": { - "passed": 0, + "passed": 55, "total": 55 } }, @@ -1292,7 +1292,7 @@ problem_mapping = { "total": 102 }, "Python: k_largest_in_heap.py": { - "passed": 0, + "passed": 102, "total": 102 } } @@ -1308,7 +1308,7 @@ problem_mapping = { "total": 314 }, "Python: search_first_key.py": { - "passed": 0, + "passed": 314, "total": 314 } }, @@ -1322,7 +1322,7 @@ problem_mapping = { "total": 501 }, "Python: search_entry_equal_to_index.py": { - "passed": 0, + "passed": 501, "total": 501 } }, @@ -1336,7 +1336,7 @@ problem_mapping = { "total": 307 }, "Python: search_shifted_sorted_array.py": { - "passed": 0, + "passed": 307, "total": 307 } }, @@ -1350,7 +1350,7 @@ problem_mapping = { "total": 2000 }, "Python: int_square_root.py": { - "passed": 0, + "passed": 2000, "total": 2000 } }, @@ -1364,7 +1364,7 @@ problem_mapping = { "total": 11003 }, "Python: real_square_root.py": { - "passed": 0, + "passed": 11003, "total": 11003 } }, @@ -1378,7 +1378,7 @@ problem_mapping = { "total": 194 }, "Python: search_row_col_sorted_matrix.py": { - "passed": 0, + "passed": 194, "total": 194 } }, @@ -1392,7 +1392,7 @@ problem_mapping = { "total": 501 }, "Python: search_for_min_max_in_array.py": { - "passed": 0, + "passed": 501, "total": 501 } }, @@ -1406,7 +1406,7 @@ problem_mapping = { "total": 503 }, "Python: kth_largest_in_array.py": { - "passed": 0, + "passed": 503, "total": 503 } }, @@ -1420,7 +1420,7 @@ problem_mapping = { "total": 100 }, "Python: absent_value_array.py": { - "passed": 0, + "passed": 100, "total": 100 } }, @@ -1434,7 +1434,7 @@ problem_mapping = { "total": 50 }, "Python: search_for_missing_element.py": { - "passed": 0, + "passed": 50, "total": 50 } } @@ -1464,7 +1464,7 @@ problem_mapping = { "total": 305 }, "Python: is_string_permutable_to_palindrome.py": { - "passed": 0, + "passed": 305, "total": 305 } }, @@ -1478,7 +1478,7 @@ problem_mapping = { "total": 212 }, "Python: is_anonymous_letter_constructible.py": { - "passed": 0, + "passed": 212, "total": 212 } }, @@ -1492,7 +1492,7 @@ problem_mapping = { "total": 101 }, "Python: lru_cache.py": { - "passed": 0, + "passed": 101, "total": 101 } }, @@ -1506,7 +1506,7 @@ problem_mapping = { "total": 948 }, "Python: lowest_common_ancestor_close_ancestor.py": { - "passed": 0, + "passed": 948, "total": 948 } }, @@ -1520,7 +1520,7 @@ problem_mapping = { "total": 505 }, "Python: nearest_repeated_entries.py": { - "passed": 0, + "passed": 505, "total": 505 } }, @@ -1534,7 +1534,7 @@ problem_mapping = { "total": 904 }, "Python: smallest_subarray_covering_set.py": { - "passed": 0, + "passed": 904, "total": 904 } }, @@ -1548,7 +1548,7 @@ problem_mapping = { "total": 629 }, "Python: smallest_subarray_covering_all_values.py": { - "passed": 0, + "passed": 629, "total": 629 } }, @@ -1562,7 +1562,7 @@ problem_mapping = { "total": 987 }, "Python: longest_subarray_with_distinct_values.py": { - "passed": 0, + "passed": 987, "total": 987 } }, @@ -1576,7 +1576,7 @@ problem_mapping = { "total": 203 }, "Python: longest_contained_interval.py": { - "passed": 0, + "passed": 203, "total": 203 } }, @@ -1590,7 +1590,7 @@ problem_mapping = { "total": 171 }, "Python: string_decompositions_into_dictionary_words.py": { - "passed": 0, + "passed": 171, "total": 171 } }, @@ -1604,7 +1604,7 @@ problem_mapping = { "total": 7 }, "Python: collatz_checker.py": { - "passed": 0, + "passed": 7, "total": 7 } }