├── graphs ├── has_path │ ├── __init__.py │ ├── README.md │ ├── has_path_v3.py │ ├── has_path_v1.py │ └── has_path_v2.py ├── shortest_path │ └── __init__.py ├── largest_component │ ├── __init__.py │ └── README.md ├── connected_components │ ├── README.md │ └── connected_components_count_v2.py ├── island_count │ └── README.md ├── has_cycle │ └── README.md ├── longest_path │ └── README.md ├── minimum_island │ └── README.md ├── knight_attack │ └── README.md ├── semesters_required │ └── README.md └── prereqs_available │ └── README.md ├── binary_tree ├── leaf_list │ ├── __init__.py │ ├── leaf_list_v2.py │ ├── leaf_list_v1.py │ └── README.md ├── tests │ └── __init__.py ├── tree_sum │ ├── __init__.py │ ├── README.md │ ├── tree_sum_v2.py │ ├── tree_sum_v3.py │ └── tree_sum_v1.py ├── all_tree_paths │ └── __init__.py ├── invert_tree │ ├── __init__.py │ └── README.md ├── level_averages │ ├── __init__.py │ ├── level_averages_v1.py │ ├── level_averages_v2.py │ └── README.md ├── symmetric_tree │ ├── __init__.py │ └── README.md ├── tree_includes │ ├── __init__.py │ ├── tree_includes_v2.py │ └── tree_includes_v1.py ├── tree_levels │ ├── __init__.py │ ├── README.md │ └── tree_levels_v1.py ├── tree_min_value │ ├── __init__.py │ ├── README.md │ ├── tree_min_value_v2.py │ ├── tree_min_value_v3.py │ └── tree_min_value_v1.py ├── bottom_right_value │ └── __init__.py ├── depth_first_values │ ├── __init__.py │ └── README.md ├── diameter_of_tree │ ├── __init__.py │ ├── README.md │ └── diameter_of_tree.py ├── max_tree_path_sum │ ├── __init__.py │ └── README.md ├── tree_path_finder │ └── __init__.py ├── tree_value_count │ ├── __init__.py │ ├── tree_value_count_v1.py │ ├── tree_value_count_v2.py │ ├── README.md │ └── tree_value_count_v3.py ├── breadth_first_values │ ├── ___init__.py │ └── README.md ├── max_root_leaf_path_sum │ ├── __init__.py │ └── README.md ├── serialise_deserialise_tree │ ├── __init__.py │ └── README.md └── how_high │ ├── how_high_v6.py │ ├── how_high_v7.py │ ├── how_high_v5.py │ ├── how_high_v4.py │ ├── how_high_v3.py │ └── README.md ├── introduction ├── is_prime │ ├── __init__.py │ ├── is_prime.py │ └── README.md ├── tests │ ├── __init__.py │ ├── test_hey_programmer.py │ ├── test_is_prime.py │ └── test_max_value.py ├── max_value │ ├── __init__.py │ ├── max_value_v2.py │ ├── max_value_v1.py │ └── README.md └── hey_programmer │ ├── __init__.py │ ├── fstring.py │ ├── concatenation.py │ └── README.md ├── linked_lists ├── sum_list │ ├── __init__.py │ ├── README.md │ ├── sum_list_v2.py │ └── sum_list_v1.py ├── tests │ ├── __init__.py │ ├── test_is_univalue_list.py │ ├── test_reverse_linked_list.py │ ├── test_sum_list.py │ ├── test_traversal.py │ ├── test_merge_lists.py │ ├── test_get_node_values.py │ └── test_linked_list_find.py ├── merge_lists │ ├── __init__.py │ └── README.md ├── remove_node │ ├── __init__.py │ ├── README.md │ └── remove_node_v2.py ├── traversal │ ├── __init__.py │ ├── README.md │ └── traversal_v1.py ├── zipper_lists │ └── __init__.py ├── add_linked_lists │ └── __init__.py ├── create_linked_list │ ├── __init__.py │ └── README.md ├── get_node_value │ ├── __init__.py │ ├── README.md │ ├── get_node_value_v2.py │ └── get_node_value_v1.py ├── is_univalue_list │ ├── __init__.py │ ├── README.md │ ├── is_univalue_list_v1.py │ └── is_univalue_list_v3.py ├── linked_list_cycle │ ├── __init__.py │ └── README.md ├── linked_list_find │ ├── __init__.py │ ├── README.md │ ├── linked_list_find_v1.py │ └── linked_list_find_v2.py ├── linked_list_middle │ ├── __init__.py │ ├── README.md │ └── linked_list_middle.py ├── longest_streak │ ├── __init__.py │ ├── longest_streak_v4.py │ ├── README.md │ └── longest_streak_v3.py ├── remove_nth_node │ ├── __init__.py │ └── README.md ├── palindrome_linked_list │ ├── __init__.py │ └── README.md ├── reverse_linked_list │ ├── __init__.py │ └── README.md └── insert_node │ └── README.md ├── stacks ├── nesting_score │ ├── __init__.py │ └── README.md ├── reverse_stack │ ├── __init__.py │ └── README.md ├── befitting_brackets │ ├── __init__.py │ └── README.md ├── decompress_braces │ ├── __init__.py │ └── README.md └── paired_parentheses │ ├── __init__.py │ └── README.md ├── arrays_and_strings ├── tests │ ├── __init__.py │ ├── test_compress.py │ ├── test_pair_product.py │ ├── test_five_sort.py │ ├── test_pair_sum.py │ ├── test_anagrams.py │ ├── test_most_frequent_char.py │ └── test_intersection.py ├── anagrams │ ├── __init__.py │ ├── anagrams_v3.py │ ├── README.md │ └── anagrams_v2.py ├── compress │ ├── __init__.py │ └── README.md ├── five_sort │ ├── __init__.py │ └── README.md ├── happy_number │ └── __init__.py ├── intersection │ ├── __init__.py │ ├── README.md │ ├── intersection_v4.py │ ├── intersection_v3.py │ ├── intersection_v2.py │ └── intersection_v1.py ├── pair_product │ ├── __init__.py │ └── README.md ├── pair_sum │ ├── __init__.py │ ├── README.md │ └── pair_sum_v1.py ├── three_sum │ ├── __init__.py │ └── README.md ├── uncompress │ ├── __init__.py │ └── README.md ├── first_bad_version │ ├── __init__.py │ └── README.md ├── circular_array_loop │ ├── __init__.py │ └── README.md ├── find_duplicate_number │ ├── __init__.py │ ├── README.md │ └── find_duplicate_number.py ├── longest_palindrome │ ├── __init__.py │ └── README.md ├── most_frequent_char │ ├── __init__.py │ ├── README.md │ ├── most_frequent_char_v2.py │ └── most_frequent_char_v3.py ├── sum_of_three_values │ ├── __init__.py │ └── README.md ├── container_with_most_water │ ├── __init__.py │ └── README.md └── search_rotated_sorted_array │ └── __init__.py ├── dynamic_programming ├── 01_knapsack │ ├── __init__.py │ └── README.md ├── 01_matrix │ ├── __init__.py │ └── README.md ├── count_bits │ ├── __init__.py │ └── README.md ├── word_break │ ├── __init__.py │ └── README.md ├── house_robber │ ├── __init__.py │ └── README.md ├── palindromic_substrings │ ├── __init__.py │ └── README.md ├── summing_squares │ ├── summing_squares_v1.py │ └── README.md ├── min_change │ ├── min_change_v1.py │ └── README.md ├── fib │ └── README.md ├── quickest_concat │ ├── quickest_concat_v2.py │ └── README.md ├── tribonacci │ └── README.md ├── overlap_subsequence │ └── README.md ├── max_palin_subsequence │ └── README.md ├── counting_change │ └── README.md ├── sum_possible │ └── README.md ├── can_concat │ └── README.md ├── non_adjacent_sum │ └── README.md ├── array_stepper │ └── README.md └── knightly_number │ └── README.md ├── .gitignore ├── PULL_REQUEST_TEMPLATE.md └── pyproject.toml /graphs/has_path/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/leaf_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tree_sum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /graphs/shortest_path/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /introduction/is_prime/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /introduction/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/sum_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stacks/nesting_score/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stacks/reverse_stack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/all_tree_paths/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/invert_tree/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/level_averages/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/symmetric_tree/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tree_includes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tree_levels/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tree_min_value/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /graphs/largest_component/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /introduction/max_value/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/merge_lists/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/remove_node/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/traversal/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/zipper_lists/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stacks/befitting_brackets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stacks/decompress_braces/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stacks/paired_parentheses/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/anagrams/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/compress/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/five_sort/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/happy_number/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/intersection/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/pair_product/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/pair_sum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/three_sum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/uncompress/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/bottom_right_value/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/depth_first_values/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/diameter_of_tree/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/max_tree_path_sum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tree_path_finder/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/tree_value_count/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dynamic_programming/01_knapsack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dynamic_programming/01_matrix/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dynamic_programming/count_bits/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dynamic_programming/word_break/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /introduction/hey_programmer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/add_linked_lists/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/create_linked_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/get_node_value/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/is_univalue_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/linked_list_cycle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/linked_list_find/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/linked_list_middle/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/longest_streak/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/remove_nth_node/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/first_bad_version/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/breadth_first_values/___init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/max_root_leaf_path_sum/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dynamic_programming/house_robber/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/palindrome_linked_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /linked_lists/reverse_linked_list/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/circular_array_loop/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/find_duplicate_number/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/longest_palindrome/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/most_frequent_char/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/sum_of_three_values/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /binary_tree/serialise_deserialise_tree/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/container_with_most_water/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /arrays_and_strings/search_rotated_sorted_array/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dynamic_programming/palindromic_substrings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .mypy_cache/ 3 | .venv 4 | venv/ -------------------------------------------------------------------------------- /introduction/hey_programmer/fstring.py: -------------------------------------------------------------------------------- 1 | def greet(s): 2 | return f"hey {s}" -------------------------------------------------------------------------------- /introduction/hey_programmer/concatenation.py: -------------------------------------------------------------------------------- 1 | def greet(s): 2 | return "hey " + s -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What does this PR do? 2 | 3 | - 4 | 5 | ## Related issue(s) 6 | 7 | -N/A 8 | 9 | -------------------------------------------------------------------------------- /introduction/max_value/max_value_v2.py: -------------------------------------------------------------------------------- 1 | def max_value(nums): 2 | 3 | return max(nums) 4 | 5 | # TODO: Add time and space complexity -------------------------------------------------------------------------------- /introduction/max_value/max_value_v1.py: -------------------------------------------------------------------------------- 1 | def max_value(nums): 2 | maximum = float('-inf')#to cater to negative integers 3 | for x in nums: 4 | if x>maximum: 5 | maximum=x 6 | 7 | return maximum 8 | 9 | 10 | # TODO: Add time and space complexity 11 | -------------------------------------------------------------------------------- /linked_lists/remove_nth_node/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given a singly linked list, remove the `nth` node from the end of the list and return its head. 4 | 5 | ### Constraints 6 | Let `k` be the number of nodes in a linked list. 7 | 8 | > 1 ≤ k ≤ 103 9 | 10 | > −103 ≤ Node.value ≤ 103 -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "structy" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Agatha Bahati"] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | pytest = "^7.2.1" 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /binary_tree/symmetric_tree/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given the root of a binary tree, check whether it is a symmetric tree. A symmetric tree refers to a tree that is the mirror of itself, i.e., symmetric around its root. 4 | 5 | 6 | ### Constraints 7 | 8 | > 1 ≤ Number of nodes in the tree ≤ 500. 9 | 10 | > −1000 ≤ Node.value ≤ 1000 -------------------------------------------------------------------------------- /introduction/tests/test_hey_programmer.py: -------------------------------------------------------------------------------- 1 | from hey_programmer.fstring import greet 2 | 3 | test_case=[ 4 | ('alvin','hey alvin'), 5 | ('jason', 'hey jason'), 6 | ('how now brown cow', 'hey how now brown cow'), 7 | 8 | ] 9 | 10 | def test_hey_programmer(): 11 | for test in test_case: 12 | assert greet(test[0])==test[1] 13 | -------------------------------------------------------------------------------- /binary_tree/invert_tree/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given the root node of a binary tree, transform the tree by swapping each node’s left and right subtrees, thus creating a mirror image of the original tree. Return the root of the transformed tree. 4 | 5 | 6 | ### Constraints 7 | 8 | > 0≤ Number of nodes in the tree ≤100≤100 9 | 10 | > −1000 ≤ Node.value ≤ 1000 -------------------------------------------------------------------------------- /introduction/is_prime/is_prime.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, floor 2 | 3 | def is_prime(n): 4 | if n<2: 5 | return False 6 | # *Best explanation for using sqrt: https://stackoverflow.com/a/54544012/14753649 7 | for divisor in range(2,floor(sqrt(n)) +1): 8 | if n%divisor==0: 9 | return False 10 | 11 | return True 12 | 13 | # TODO: Add time and space complexity -------------------------------------------------------------------------------- /introduction/hey_programmer/README.md: -------------------------------------------------------------------------------- 1 | ## hey programmer 2 | 3 | Write a function `greet` that takes in a string argument, s, and returns the string "hey s". No tricks here. 4 | 5 | ### test_00: 6 | 7 | `greet("alvin")` 8 | 9 | ### test_01: 10 | 11 | `greet("jason")` 12 | 13 | ### test_02: 14 | 15 | `greet("jason")` 16 | 17 | ### test_03: 18 | 19 | `greet("how now brown cow")` 20 | -------------------------------------------------------------------------------- /arrays_and_strings/anagrams/anagrams_v3.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import Dict 3 | 4 | def anagrams(s1:str, s2:str) -> bool: 5 | s1_counter = build_dict(s1) 6 | s2_counter = build_dict(s2) 7 | 8 | return s1_counter == s2_counter 9 | 10 | def build_dict(s:str) -> Dict: 11 | s_dict = defaultdict(int) 12 | 13 | for char in s: 14 | s_dict[char] += 1 15 | 16 | return s_dict 17 | -------------------------------------------------------------------------------- /linked_lists/linked_list_middle/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given the `head` of a singly linked list, return the middle node of the linked list. If the number of nodes in the linked list is even, there will be two middle nodes, so return the second one. 4 | 5 | ### Constraints 6 | Let `n` be the number of nodes in a linked list. 7 | 8 | > 1 ≤ n ≤ 100 9 | 10 | > 1 ≤ node.data ≤ 100 11 | 12 | > head != NULL 13 | 14 | 15 | -------------------------------------------------------------------------------- /linked_lists/linked_list_cycle/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Check whether or not a linked list contains a cycle. If a cycle exists, return `TRUE`. Otherwise, return `FALSE`. The cycle means that at least one node can be reached again by traversing the next pointer. 4 | 5 | ### Constraints 6 | Let `n` be the number of nodes in a linked list. 7 | 8 | > 0 ≤ n ≤ 500 9 | 10 | > −105 ≤ Node.data ≤ 105 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /introduction/tests/test_is_prime.py: -------------------------------------------------------------------------------- 1 | from is_prime.is_prime import is_prime 2 | 3 | test_case = [ 4 | (2,True), 5 | (3,True), 6 | (4,False), 7 | (5,True), 8 | (6,False), 9 | (7,True), 10 | (8,False), 11 | (25,False), 12 | (31,True), 13 | (2017,True), 14 | (2048,False), 15 | (1,False), 16 | (713,False), 17 | ] 18 | 19 | def test_is_prime(): 20 | for test in test_case: 21 | assert is_prime(test[0])==test[1] 22 | -------------------------------------------------------------------------------- /dynamic_programming/01_matrix/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given an `m×n` binary matrix, `mat`, find the distance from each cell to the nearest 0. The distance between two adjacent cells is 1. Cells to the left, right, above, and below the current cell will be considered adjacent. 4 | 5 | ### **Constraints**: 6 | 7 | 1 ≤ mat.row , mat.col ≤ 50 8 | 9 | 1 ≤ mat.row * mat.col ≤ 2500 10 | 11 | mat[i][j] ∈ {0,1} 12 | 13 | There is at least one 0 in mat. -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_compress.py: -------------------------------------------------------------------------------- 1 | from compress.compress_v1 import compress 2 | 3 | test_case = [ 4 | ('ccaaatsss','2c3at3s'), 5 | ('ssssbbz','4s2bz'), 6 | ('ppoppppp','2po5p'), 7 | ('nnneeeeeeeeeeeezz','3n12e2z'), 8 | ('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy','127y') 9 | ] 10 | 11 | def test_compress(): 12 | for test in test_case: 13 | assert compress(test[0])==test[1] 14 | -------------------------------------------------------------------------------- /binary_tree/diameter_of_tree/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given a binary tree, you need to compute the length of the tree’s diameter. The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or may not pass through the root. 4 | 5 | **Note**: The length of the path between two nodes is represented by the number of edges between them. 6 | 7 | 8 | ### Constraints 9 | 10 | > The number of nodes in the tree is in the range [1,500]. 11 | 12 | > −100 ≤ Node.value ≤ 100 -------------------------------------------------------------------------------- /linked_lists/palindrome_linked_list/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given the `head` of a linked list, your task is to check whether the linked list is a palindrome or not. Return `TRUE` if the linked list is a palindrome; otherwise, return `FALSE`. 4 | 5 | **Note**: The input linked list prior to the checking process should be identical to the list after the checking process has been completed. 6 | 7 | 8 | ### Constraints 9 | Let n be the number of nodes in a linked list. 10 | 11 | > 1 ≤ n ≤ 500 12 | 13 | > 0 ≤ Node.val ≤ 9. -------------------------------------------------------------------------------- /introduction/tests/test_max_value.py: -------------------------------------------------------------------------------- 1 | from max_value.max_value_v1 import max_value 2 | from max_value.max_value_v2 import max_value as max_value2 3 | test_case = [ 4 | 5 | ([4, 7, 2, 8, 10, 9],10), 6 | ([10, 5, 40, 40.3],40.3), 7 | ([-5, -2, -1, -11],-1), 8 | ([42],42), 9 | ([1000, 8],1000), 10 | ([1000, 8, 9000],9000), 11 | ([2, 5, 1, 1, 4],5), 12 | 13 | ] 14 | 15 | def test_max_value(): 16 | for test in test_case: 17 | assert max_value(test[0])==test[1] 18 | assert max_value2(test[0])==test[1] -------------------------------------------------------------------------------- /dynamic_programming/summing_squares/summing_squares_v1.py: -------------------------------------------------------------------------------- 1 | from math import sqrt, floor 2 | def summing_squares(target: int) -> int: 3 | if target == 0: 4 | return 0 5 | 6 | min_squares = float('inf') 7 | 8 | for num in range(1, floor(sqrt(target) + 1)): 9 | num_square = num * num 10 | remainder = target - num_square 11 | no_of_squares = 1 + summing_squares(remainder) 12 | if no_of_squares < min_squares: 13 | min_squares = no_of_squares 14 | 15 | return min_squares 16 | -------------------------------------------------------------------------------- /binary_tree/how_high/how_high_v6.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | def how_high(node): 3 | if node is None: 4 | return -1 5 | 6 | queue = deque([(node, 0)]) 7 | 8 | while queue: 9 | current_node, level = queue.popleft() 10 | 11 | if current_node.left: 12 | queue.append((current_node.left, level+1)) 13 | 14 | if current_node.right: 15 | queue.append((current_node.right, level+1)) 16 | 17 | return level 18 | 19 | """ 20 | n - # of nodes 21 | Time: O(n) 22 | Space: O(n) 23 | """ -------------------------------------------------------------------------------- /binary_tree/how_high/how_high_v7.py: -------------------------------------------------------------------------------- 1 | def how_high(root): 2 | if root is None: 3 | return -1 4 | 5 | stack = [(root,0)] 6 | max_height = float("-inf") 7 | 8 | while stack: 9 | current, current_height = stack.pop() 10 | 11 | if current.left is None and current.right is None: 12 | if current_height > max_height: 13 | max_height = current_height 14 | 15 | if current.right: 16 | stack.append((current.right, current_height+1)) 17 | 18 | if current.left: 19 | stack.append((current.left, current_height+1)) 20 | 21 | return max_height -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_pair_product.py: -------------------------------------------------------------------------------- 1 | from pair_product.pair_product_v1 import pair_product 2 | from pair_product.pair_product_v2 import pair_product_v2 3 | 4 | 5 | test_case = [ 6 | ([3, 2, 5, 4, 1], 8,(1,3)), 7 | ([3, 2, 5, 4, 1], 10,(1,2)), 8 | ([4, 7, 9, 2, 5, 1], 5,(4,5)), 9 | ([4, 7, 9, 2, 5, 1], 35,(1,4)), 10 | ([3, 2, 5, 4, 1], 10,(1,2)), 11 | ] 12 | 13 | def test_pair_product(): 14 | for test in test_case: 15 | assert pair_product(test[0],test[1])==test[2] 16 | assert pair_product_v2(test[0],test[1]) == test[2] or test[2][::-1] -------------------------------------------------------------------------------- /dynamic_programming/count_bits/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | For a given positive integer, n, your task is to return an array of length **n+1** such that for each `x` where `0 ≤ x ≤ n` , `result[x]` is the count of 1s in the binary representation of `x`. 4 | 5 | Constraints: 6 | 7 | > 0 ≤ n ≤ 104 8 | 9 | ### **Example** 10 | 11 | Input: n = 3 12 | Output = [0,1,1,2] 13 | x->0: 0 (no ones) 14 | x->1: 1 (1 one) 15 | x->2: 10 (1 one) 16 | x->3: 11 (2 ones) 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /linked_lists/tests/test_is_univalue_list.py: -------------------------------------------------------------------------------- 1 | from is_univalue_list.is_univalue_list_v1 import is_univalue_list 2 | from traversal.traversal_v1 import Node 3 | 4 | test_case = [True, False, True, False, True] 5 | 6 | def test_is_univalue_00(): 7 | a = Node(7) 8 | b = Node(7) 9 | c = Node(7) 10 | 11 | a.next = b 12 | b.next = c 13 | assert is_univalue_list(a) == test_case[0] 14 | 15 | def test_is_univalue_01(): 16 | a = Node(7) 17 | b = Node(7) 18 | c = Node(4) 19 | 20 | a.next = b 21 | b.next = c 22 | 23 | assert is_univalue_list(a) == test_case[1] 24 | 25 | -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_five_sort.py: -------------------------------------------------------------------------------- 1 | from five_sort.five_sort import five_sort 2 | 3 | test_case = [ 4 | ([12, 5, 1, 5, 12, 7],[12, 7, 1, 12, 5, 5]), 5 | ([5, 2, 5, 6, 5, 1, 10, 2, 5, 5],[2, 2, 10, 6, 1, 5, 5, 5, 5, 5]), 6 | ([5, 5, 5, 1, 1, 1, 4],[4, 1, 1, 1, 5, 5, 5]), 7 | ([5, 5, 6, 5, 5, 5, 5],[6, 5, 5, 5, 5, 5, 5]), 8 | ([5, 1, 2, 5, 5, 3, 2, 5, 1, 5, 5, 5, 4, 5],[4, 1, 2, 1, 2, 3, 5, 5, 5, 5, 5, 5, 5, 5]), 9 | ([5] * 20000+[4] * 20000, [4] * 20000 + [5] * 20000), 10 | ] 11 | 12 | def test_five_sort(): 13 | for test in test_case: 14 | assert five_sort(test[0]) == test[1] 15 | -------------------------------------------------------------------------------- /binary_tree/how_high/how_high_v5.py: -------------------------------------------------------------------------------- 1 | def how_high(node): 2 | if node is None: 3 | return -1 4 | distance = 0 5 | max = 0 6 | stack = [(node,distance)] 7 | 8 | while stack: 9 | x, distance = stack.pop() 10 | 11 | if x.left: 12 | stack.append((x.left, distance+1)) 13 | if x.right: 14 | stack.append((x.right, distance+1)) 15 | 16 | if distance > max: 17 | max = distance 18 | 19 | 20 | return max 21 | 22 | """ 23 | 24 | n = number of nodes 25 | Time: O(n) 26 | Space: O(n) 27 | 28 | """ -------------------------------------------------------------------------------- /binary_tree/how_high/how_high_v4.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def how_high(node): 4 | if node is None: 5 | return -1 6 | 7 | level = 0 8 | queue = deque([(node,0)]) 9 | 10 | while queue: 11 | 12 | level_size = len(queue) 13 | for _ in range(level_size): 14 | x, level = queue.popleft() 15 | if x.right: 16 | queue.append((x.right, level+1)) 17 | if x.left: 18 | queue.append((x.left, level+1)) 19 | 20 | return level 21 | 22 | """ 23 | 24 | n = number of nodes 25 | Time: O(n) 26 | Space: O(n) 27 | 28 | """ 29 | 30 | -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_pair_sum.py: -------------------------------------------------------------------------------- 1 | from pair_sum.pair_sum_v1 import pair_sum as ps1 2 | from pair_sum.pair_sum_v2 import pair_sum as ps2 3 | from pair_sum.pair_sum_v3 import pair_sum as ps3 4 | 5 | test_case=[ 6 | ([3, 2, 5, 4, 1], 8,(0,2)), 7 | ([4, 7, 9, 2, 5, 1],3, (3,5)), 8 | ([1, 6, 7, 2], 13,(1,2)), 9 | ([9, 9], 18,(0,1)), 10 | ([6, 4, 2, 8], 12,(1,3)), 11 | ] 12 | 13 | def test_pair_sum(): 14 | for test in test_case: 15 | # *TODO fix failing test for ps1 16 | # assert ps1(test[0], test[1])==test[2] 17 | # assert ps2(test[0], test[1])==test[2] 18 | assert ps3(test[0], test[1])==test[2] -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_anagrams.py: -------------------------------------------------------------------------------- 1 | from anagrams import anagrams_v1 as anagrams 2 | 3 | test_case=[ 4 | ('paper', 'reapa',False), 5 | ('restful', 'fluster',True), 6 | ('cats', 'tocs',False), 7 | ('monkeyswrite', 'newyorktimes',True), 8 | ('elbow', 'below', True), 9 | ('tax', 'taxi',False), 10 | ('taxi', 'tax', False), 11 | ('night', 'thing', True), 12 | ('abbc', 'aabc', False), 13 | ('po', 'popp', False), 14 | ('pp', 'oo',False) 15 | 16 | ] 17 | 18 | def test_anagrams(): 19 | for item in test_case: 20 | assert anagrams.anagrams(item[0],item[1]) == item[2] 21 | -------------------------------------------------------------------------------- /binary_tree/serialise_deserialise_tree/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Serialize a given binary tree to a file and deserialize it back to a tree. Make sure that the original and the deserialized trees are identical. 4 | 5 | Serialize: Write the tree to a file. 6 | 7 | Deserialize: Read from a file and reconstruct the tree in memory. 8 | 9 | Serialize the tree into a list of integers, and then, deserialize it back from the list to a tree. For simplicity’s sake, there’s no need to write the list to the files. 10 | 11 | 12 | ### Constraints 13 | 14 | > The number of nodes in the tree is in the range [0,500]. 15 | 16 | > −1000 ≤ Node.value ≤ 1000 -------------------------------------------------------------------------------- /linked_lists/longest_streak/longest_streak_v4.py: -------------------------------------------------------------------------------- 1 | def longest_streak(head): 2 | if head is None: 3 | return 0 4 | 5 | if head.next is None: 6 | return 1 7 | 8 | comparison_val = head.val 9 | curr_streak = 1 10 | max_streak = 0 11 | current = head.next 12 | while current: 13 | if current.val == comparison_val: 14 | curr_streak += 1 15 | else: 16 | if curr_streak > max_streak: 17 | max_streak = curr_streak 18 | comparison_val = current.val 19 | curr_streak = 1 20 | 21 | current = current.next 22 | return max_streak -------------------------------------------------------------------------------- /dynamic_programming/min_change/min_change_v1.py: -------------------------------------------------------------------------------- 1 | def min_change(amount:int, coins:list[int]) -> int: 2 | ans = _min_change(amount, coins) 3 | if ans == float('inf'): 4 | return -1 5 | return ans 6 | 7 | def _min_change(amount:int, coins:list[int]) -> int: 8 | if amount == 0: 9 | return 0 10 | 11 | if amount < 0: 12 | return float('inf') 13 | min_coins = float('inf') 14 | for coin in coins: 15 | remaining_amount = amount - coin 16 | num_of_coins = 1 + _min_change(remaining_amount, coins) 17 | if num_of_coins < min_coins: 18 | min_coins = num_of_coins 19 | 20 | return min_coins 21 | -------------------------------------------------------------------------------- /linked_lists/create_linked_list/README.md: -------------------------------------------------------------------------------- 1 | # create linked list 2 | 3 | Write a function, `create_linked_list`, that takes in a list of values as an argument. The function should create a linked list containing each item of the list as the values of the nodes. The function should return the head of the linked list. 4 | 5 | ## test_00: 6 | ```python 7 | create_linked_list(["h", "e", "y"]) 8 | # h -> e -> y 9 | ``` 10 | ## test_01: 11 | ```python 12 | create_linked_list([1, 7, 1, 8]) 13 | # 1 -> 7 -> 1 -> 8 14 | ``` 15 | 16 | ## test_02: 17 | 18 | ```python 19 | create_linked_list(["a"]) 20 | # a 21 | ``` 22 | 23 | ## test_03: 24 | 25 | ```python 26 | create_linked_list([]) 27 | # null 28 | ``` 29 | -------------------------------------------------------------------------------- /arrays_and_strings/first_bad_version/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | The latest version of a software product fails the quality check. Since each version is developed upon the previous one, all the versions created after a bad version are also considered bad. 4 | 5 | Suppose you have n versions with the IDs [1,2,...,n], and you have access to an API function that returns TRUE if the argument is the ID of a bad version. 6 | 7 | Find the first bad version that is causing all the later ones to be bad. Additionally, the solution should also return the number of API calls made during the process and should minimize the number of API calls too. 8 | 9 | ### Constraints 10 | 11 | > 1 ≤ first bad version ≤ n ≤ 105 -------------------------------------------------------------------------------- /arrays_and_strings/sum_of_three_values/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given an array of integers, `nums`, and an integer value, `target`, determine if there are any three integers in nums whose sum is equal to the `target`, that is, 4 | 5 | nums[i] + nums[j] + nums[k] == target 6 | 7 | Return TRUE if three such integers exist in the array. Otherwise, return FALSE. 8 | 9 | Note: A valid triplet consists of elements with distinct indexes. This means, for the triplet nums[i], nums[j], and nums[k], i != j, i != k and j != k. 10 | 11 | **Constraints**: 12 | 13 | 14 | > 3 ≤ nums.length ≤ 500 15 | 16 | > 103 ≤ nums[i] ≤ 103 17 | 18 | > −103 ≤ target ≤ 103 19 | 20 | -------------------------------------------------------------------------------- /introduction/max_value/README.md: -------------------------------------------------------------------------------- 1 | ## max-value 2 | 3 | Write a function, `max_value`, that takes in list of numbers as an argument. The function should return the largest number in the list. 4 | 5 | Solve this without using any built-in list methods. 6 | 7 | You can assume that the list is non-empty. 8 | 9 | ## test_00: 10 | 11 | `max_value([4, 7, 2, 8, 10, 9])` 12 | 13 | ## test_01: 14 | 15 | `max_value([10, 5, 40, 40.3])` 16 | 17 | ## test_02: 18 | 19 | `max_value([-5, -2, -1, -11]) ` 20 | 21 | ## test_03: 22 | 23 | `max_value([42])` 24 | 25 | ## test_04: 26 | 27 | `max_value([1000, 8])` 28 | 29 | ## test_05: 30 | 31 | `max_value([1000, 8, 9000])` 32 | 33 | ## test_06: 34 | 35 | `max_value([2, 5, 1, 1, 4])` 36 | -------------------------------------------------------------------------------- /stacks/reverse_stack/README.md: -------------------------------------------------------------------------------- 1 | # Reverse Stack (Medium) 2 | 3 | ## **Problem Statement** 4 | 5 | Given a stack represented by an array, implement a function `reverse_stack` that reverses the elements of the stack **in-place** using recursion. 6 | 7 | ### **Example** 8 | Given an input stack which is a list of integers, implement the function `reverse_stack` that reverses the order of elements in this stack: 9 | 10 | Input = [1, 2, 3, 4, 5] 11 | Expected output = [5, 4, 3, 2, 1] 12 | 13 | 14 | 15 | **Constraints** 16 | 17 | * 0 <= `stack.length` <= 100 18 | * -2^31 <= `stack[i]` <= 2^31 - 1 19 | 20 | **Follow Up** 21 | 22 | * Can you solve this problem iteratively? (This might have lower space complexity) 23 | -------------------------------------------------------------------------------- /arrays_and_strings/longest_palindrome/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given a string `s` which consists of lowercase or uppercase letters, return the length of the **longest** 4 | palindrome 5 | that can be built with those letters. 6 | 7 | Letters are case sensitive, for example, `"Aa"` is not considered a palindrome. 8 | 9 | ### Example 1 10 | Input: s = "abccccdd" 11 | Output: 7 12 | Explanation: One longest palindrome that can be built is "dccaccd", whose length is 7. 13 | 14 | ### Example 2 15 | Input: s = "a" 16 | Output: 1 17 | Explanation: The longest palindrome that can be built is "a", whose length is 1. 18 | 19 | ### Constraints 20 | - 1 <= s.length <= 2000 21 | - `s` consists of lowercase and/or uppercase English letters only. -------------------------------------------------------------------------------- /binary_tree/max_tree_path_sum/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given the root of a binary tree, return the maximum sum of any non-empty path. 4 | 5 | A path in a binary tree is defined as follows: 6 | 7 | A sequence of nodes in which each pair of adjacent nodes must have an edge connecting them. 8 | A node can only be included in a path once at most. 9 | Including the root in the path is not compulsory. 10 | 11 | You can calculate the path sum by adding up all node values in the path. To solve this problem, calculate the maximum path sum given the root of a binary tree so that there won’t be any greater path than it in the tree. 12 | 13 | 14 | ### Constraints 15 | 16 | > 1 ≤ Number of nodes in the tree ≤ 500. 17 | 18 | > −1000 ≤ Node.value ≤ 1000 -------------------------------------------------------------------------------- /arrays_and_strings/intersection/README.md: -------------------------------------------------------------------------------- 1 | # intersection 2 | 3 | Write a function, `intersection`, that takes in two lists, a,b, as arguments. The function should return a new list containing elements that are in both of the two lists. 4 | 5 | You may assume that each input list does not contain duplicate elements. 6 | ## test_00: 7 | 8 | `intersection([4,2,1,6], [3,6,9,2,10]) # -> [2,6]` 9 | 10 | ## test_01: 11 | 12 | `intersection([2,4,6], [4,2]) # -> [2,4]` 13 | 14 | ## test_02: 15 | 16 | `intersection([4,2,1], [1,2,4,6]) # -> [1,2,4]` 17 | 18 | ## test_03: 19 | 20 | `intersection([0,1,2], [10,11]) # -> []` 21 | 22 | ## test_04: 23 | 24 | `a = [ i for i in range(0, 50000) ]` 25 | 26 | `b = [ i for i in range(0, 50000) ]` 27 | 28 | `intersection(a, b) # -> [0,1,2,3,..., 49999]` 29 | -------------------------------------------------------------------------------- /arrays_and_strings/pair_sum/README.md: -------------------------------------------------------------------------------- 1 | # pair sum 2 | 3 | Write a function, `pair_sum`, that takes in a list and a target sum as arguments. The function should return a tuple containing a pair of indices whose elements sum to the given target. The indices returned must be unique. 4 | 5 | There is guaranteed to be one such pair that sums to the target. 6 | 7 | ## test_00: 8 | 9 | `pair_sum([3, 2, 5, 4, 1], 8) # -> (0, 2)` 10 | 11 | ## test_01: 12 | 13 | `pair_sum([4, 7, 9, 2, 5, 1], 5) # -> (0, 5)` 14 | 15 | ## test_02: 16 | 17 | `pair_sum([4, 7, 9, 2, 5, 1], 3) # -> (3, 5)` 18 | 19 | ## test_03: 20 | 21 | `pair_sum([1, 6, 7, 2], 13) # -> (1, 2)` 22 | 23 | ## test_04: 24 | 25 | `pair_sum([9, 9], 18) # -> (0, 1)` 26 | 27 | ## test_05: 28 | 29 | `pair_sum([6, 4, 2, 8 ], 12) # -> (1, 3)` 30 | -------------------------------------------------------------------------------- /dynamic_programming/palindromic_substrings/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given a string, `s`, return the number of palindromic substrings contained in it. A substring is a contiguous sequence of characters in a string. A palindrome is a phrase, word, or sequence that reads the same forward and backward. 4 | 5 | ### **Constraints**: 6 | 7 | 1 ≤ s.length ≤ 1000 8 | 9 | s consists of only lowercase English characters. 10 | 11 | ### Example 12 | 13 | s = “deed” 14 | 15 | Number of substrings = 10: 16 | “d”, “e”, “e”, “d”, “de”, “ee”, “ed”, “dee”, “eed”, and “deed” 17 | 18 | Out of these 10 substrings, 6 are palindromes: 19 | “d”, “e”, “e”, “d”, “ee”, and “deed” 20 | 21 | Therefore, the number of palindromic substrings in “deed” is 6. 22 | 23 | 24 | -------------------------------------------------------------------------------- /dynamic_programming/fib/README.md: -------------------------------------------------------------------------------- 1 | # fib 2 | 3 | Write a function `fib` that takes in a number argument, **n**, and returns the n-th number of the Fibonacci sequence. 4 | 5 | The 0-th number of the sequence is 0. 6 | 7 | The 1-st number of the sequence is 1. 8 | 9 | To generate further numbers of the sequence, calculate the sum of previous two numbers. 10 | 11 | 12 | 13 | ```python 14 | fib(0); # -> 0 15 | ``` 16 | 17 | 18 | ```python 19 | fib(1); # -> 1 20 | ``` 21 | 22 | 23 | ```python 24 | fib(2); # -> 1 25 | ``` 26 | 27 | 28 | ```python 29 | fib(3); # -> 2 30 | ``` 31 | 32 | 33 | ```python 34 | fib(4); # -> 3 35 | ``` 36 | 37 | 38 | ```python 39 | fib(5); # -> 5 40 | ``` 41 | 42 | 43 | ```python 44 | fib(35); # -> 9227465 45 | ``` 46 | 47 | 48 | ```python 49 | fib(46); # -> 1836311903 50 | ``` 51 | -------------------------------------------------------------------------------- /dynamic_programming/quickest_concat/quickest_concat_v2.py: -------------------------------------------------------------------------------- 1 | def quickest_concat(s, words): 2 | result = _quickest_concat(s, words, {}) 3 | if result == float('inf'): 4 | return -1 5 | else: 6 | return result 7 | 8 | def _quickest_concat(s, words, memo): 9 | if s in memo: 10 | return memo[s] 11 | 12 | if s == '': 13 | return 0 14 | 15 | min_words = float('inf') 16 | for word in words: 17 | if s.startswith(word): 18 | suffix = s[len(word):] 19 | attempt = 1 + _quickest_concat(suffix, words, memo) 20 | min_words = min(attempt, min_words) 21 | 22 | memo[s] = min_words 23 | return min_words 24 | 25 | """ 26 | 27 | s = length of string 28 | w = # of words 29 | Time: ~O(sw) 30 | Space: O(s) 31 | 32 | """ -------------------------------------------------------------------------------- /arrays_and_strings/uncompress/README.md: -------------------------------------------------------------------------------- 1 | ## uncompress 2 | 3 | Write a function, `uncompress`, that takes in a string as an argument. The input string will be formatted into multiple groups according to the following pattern: 4 | 5 | `` 6 | 7 | `for example, '2c' or '3a'.` 8 | 9 | The function should return an uncompressed version of the string where each 'char' of a group is repeated 'number' times consecutively. You may assume that the input string is well-formed according to the previously mentioned pattern. 10 | 11 | ## test_00: 12 | 13 | `uncompress("2c3a1t")` 14 | 15 | ## test_01: 16 | 17 | `uncompress("4s2b")` 18 | 19 | ## test_02: 20 | 21 | `uncompress("2p1o5p")` 22 | 23 | ## test_03: 24 | 25 | `uncompress("3n12e2z")` 26 | 27 | ## test_04: 28 | 29 | `uncompress("127y")` 30 | -------------------------------------------------------------------------------- /linked_lists/sum_list/README.md: -------------------------------------------------------------------------------- 1 | # sum list 2 | 3 | Write a function, `sum_list`, that takes in the head of a linked list containing numbers as an argument. The function should return the total sum of all values in the linked list. 4 | ## test_00: 5 | 6 | ```python 7 | a = Node(2) 8 | b = Node(8) 9 | c = Node(3) 10 | d = Node(-1) 11 | e = Node(7) 12 | 13 | a.next = b 14 | b.next = c 15 | c.next = d 16 | d.next = e 17 | 18 | # 2 -> 8 -> 3 -> -1 -> 7 19 | 20 | sum_list(a) # 19 21 | ``` 22 | 23 | ## test_01: 24 | 25 | ```python 26 | x = Node(38) 27 | y = Node(4) 28 | 29 | x.next = y 30 | 31 | # 38 -> 4 32 | 33 | sum_list(x) # 42 34 | ``` 35 | 36 | ## test_02: 37 | 38 | ```python 39 | z = Node(100) 40 | 41 | # 100 42 | 43 | sum_list(z) # 100 44 | ``` 45 | 46 | ## test_03: 47 | 48 | ```python 49 | sum_list(None) # 0 50 | ``` 51 | -------------------------------------------------------------------------------- /linked_lists/tests/test_reverse_linked_list.py: -------------------------------------------------------------------------------- 1 | from reverse_linked_list.reverse_linked_list_v1 import reverse_list as reverse_list_v1 2 | from traversal.traversal_v1 import Node 3 | 4 | test_case = ['f','y','p'] 5 | 6 | def test_reverse_linked_list_a(): 7 | a = Node("a") 8 | b = Node("b") 9 | c = Node("c") 10 | d = Node("d") 11 | e = Node("e") 12 | f = Node("f") 13 | 14 | a.next = b 15 | b.next = c 16 | c.next = d 17 | d.next = e 18 | e.next = f 19 | 20 | assert reverse_list_v1(a) == test_case[0] 21 | 22 | def test_reverse_linked_list_x(): 23 | x = Node("x") 24 | y = Node("y") 25 | 26 | x.next = y 27 | 28 | assert reverse_list_v1(x) == test_case[1] 29 | 30 | def test_reverse_linked_list_p(): 31 | p = Node("p") 32 | 33 | assert reverse_list_v1(p) == test_case[2] 34 | -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_most_frequent_char.py: -------------------------------------------------------------------------------- 1 | from most_frequent_char.most_frequent_char_v1 import most_frequent_char as mfc1 2 | from most_frequent_char.most_frequent_char_v2 import most_frequent_char as mfc2 3 | from most_frequent_char.most_frequent_char_v3 import most_frequent_char as mfc3 4 | from most_frequent_char.most_frequent_char_v4 import most_frequent_char as mfc4 5 | 6 | test_case = [ 7 | ('bookeeper','e'), 8 | ('david','d'), 9 | ('abby','b'), 10 | ('mississippi','i'), 11 | ('potato','o'), 12 | ('eleventennine','e'), 13 | ('riverbed','r'), 14 | ] 15 | 16 | def test_most_frequent_char(): 17 | for test in test_case: 18 | # *TODO fix issue with mfc1 19 | # assert mfc1(test[0]) == test[1] 20 | assert mfc2(test[0]) == test[1] 21 | assert mfc3(test[0]) == test[1] 22 | assert mfc4(test[0]) == test[1] -------------------------------------------------------------------------------- /linked_lists/traversal/README.md: -------------------------------------------------------------------------------- 1 | # linked list values 2 | 3 | Write a function, `linked_list_values`, that takes in the head of a linked list as an argument. The function should return a list containing all values of the nodes in the linked list. 4 | 5 | ## test_00 6 | 7 | ``` 8 | a = Node("a") 9 | b = Node("b") 10 | c = Node("c") 11 | d = Node("d") 12 | 13 | a.next = b 14 | b.next = c 15 | c.next = d 16 | 17 | a -> b -> c -> d 18 | 19 | linked_list_values(a) # -> [ 'a', 'b', 'c', 'd' ] 20 | ``` 21 | 22 | ## test_01 23 | 24 | ``` 25 | x = Node("x") 26 | y = Node("y") 27 | 28 | x.next = y 29 | 30 | # x -> y 31 | 32 | linked_list_values(x) # -> [ 'x', 'y' ] 33 | ``` 34 | 35 | ## test_02 36 | 37 | ``` 38 | q = Node("q") 39 | 40 | # q 41 | 42 | linked_list_values(q) # -> [ 'q' ] 43 | ``` 44 | 45 | ## test_03 46 | 47 | ``` 48 | linked_list_values(None) # -> [ ] 49 | ``` 50 | -------------------------------------------------------------------------------- /arrays_and_strings/most_frequent_char/README.md: -------------------------------------------------------------------------------- 1 | 2 | # most frequent char 3 | 4 | Write a function, `most_frequent_char`, that takes in a string as an argument. The function should return the most frequent character of the string. If there are ties, return the character that appears earlier in the string. 5 | 6 | You can assume that the input string is non-empty. 7 | 8 | ## test_00: 9 | 10 | `most_frequent_char('bookeeper') # -> 'e'` 11 | 12 | ## test_01: 13 | 14 | `most_frequent_char('david') # -> 'd'` 15 | 16 | ## test_02: 17 | 18 | `most_frequent_char('abby') # -> 'b'` 19 | 20 | ## test_03: 21 | 22 | `most_frequent_char('mississippi') # -> 'i'` 23 | 24 | ## test_04: 25 | 26 | `most_frequent_char('potato') # -> 'o'` 27 | 28 | ## test_05: 29 | 30 | `most_frequent_char('eleventennine') # -> 'e'` 31 | 32 | ## test_06: 33 | 34 | `most_frequent_char('riverbed') # -> 'r'` 35 | -------------------------------------------------------------------------------- /dynamic_programming/tribonacci/README.md: -------------------------------------------------------------------------------- 1 | # tribonacci 2 | 3 | Write a function `tribonacci` that takes in a number argument, n, and returns the n-th number of the Tribonacci sequence. 4 | 5 | The 0-th and 1-st numbers of the sequence are both 0. 6 | 7 | The 2-nd number of the sequence is 1. 8 | 9 | To generate further numbers of the sequence, calculate the sum of previous three numbers. 10 | 11 | Solve this recursively. 12 | 13 | ```python 14 | tribonacci(0) # -> 0 15 | ``` 16 | 17 | ```python 18 | tribonacci(1) # -> 0 19 | ``` 20 | 21 | ```python 22 | tribonacci(2) # -> 1 23 | ``` 24 | 25 | ```python 26 | tribonacci(5) # -> 4 27 | ``` 28 | 29 | ```python 30 | tribonacci(7) # -> 13 31 | ``` 32 | 33 | ```python 34 | tribonacci(14) # -> 927 35 | ``` 36 | 37 | ```python 38 | tribonacci(20) # -> 35890 39 | ``` 40 | 41 | ```python 42 | tribonacci(37) # -> 1132436852 43 | ``` 44 | -------------------------------------------------------------------------------- /linked_lists/reverse_linked_list/README.md: -------------------------------------------------------------------------------- 1 | # reverse list 2 | 3 | Write a function, `reverse_list`, that takes in the head of a linked list as an argument. The function should reverse the order of the nodes in the linked list in-place and return the new head of the reversed linked list. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node("a") 9 | b = Node("b") 10 | c = Node("c") 11 | d = Node("d") 12 | e = Node("e") 13 | f = Node("f") 14 | 15 | a.next = b 16 | b.next = c 17 | c.next = d 18 | d.next = e 19 | e.next = f 20 | 21 | # a -> b -> c -> d -> e -> f 22 | 23 | reverse_list(a) # f -> e -> d -> c -> b -> a 24 | ``` 25 | 26 | ## test_01: 27 | 28 | ```python 29 | x = Node("x") 30 | y = Node("y") 31 | 32 | x.next = y 33 | 34 | # x -> y 35 | 36 | reverse_list(x) # y -> x 37 | ``` 38 | 39 | ## test_02: 40 | 41 | ```python 42 | p = Node("p") 43 | 44 | # p 45 | 46 | reverse_list(p) # p 47 | ``` 48 | -------------------------------------------------------------------------------- /dynamic_programming/overlap_subsequence/README.md: -------------------------------------------------------------------------------- 1 | # overlap subsequence 2 | 3 | Write a function, `overlap_subsequence`, that takes in two strings as arguments. The function should return the length of the longest overlapping subsequence. 4 | 5 | A subsequence of a string can be created by deleting any characters of the string, while maintaining the relative order of characters. 6 | 7 | ```python 8 | overlap_subsequence("dogs", "daogt") # -> 3 9 | ``` 10 | 11 | ```python 12 | overlap_subsequence("xcyats", "criaotsi") # -> 4 13 | ``` 14 | 15 | ```python 16 | overlap_subsequence("xfeqortsver", "feeeuavoeqr") # -> 5 17 | ``` 18 | 19 | ```python 20 | overlap_subsequence("kinfolklivemustache", "bespokekinfolksnackwave") # -> 11 21 | ``` 22 | 23 | ```python 24 | overlap_subsequence( 25 | "mumblecorebeardle 26 | "ggingsauthenticunicorn", 27 | "succulentspughumblemeditationlocavore" 28 | ) # -> 15 29 | ``` 30 | -------------------------------------------------------------------------------- /dynamic_programming/summing_squares/README.md: -------------------------------------------------------------------------------- 1 | # summing squares 2 | 3 | Write a function, `summing_squares`, that takes a target number as an argument. The function should return the minimum number of perfect squares that sum to the target. A perfect square is a number of the form (i*i) where i >= 1. 4 | 5 | For example: 1, 4, 9, 16 are perfect squares, but 8 is not perfect square. 6 | 7 | Given 12: 8 | 9 | `summing_squares(12) -> 3` 10 | 11 | The minimum squares required for 12 is three, by doing 4 + 4 + 4. 12 | 13 | Another way to make 12 is 9 + 1 + 1 + 1, but that requires four perfect squares. 14 | 15 | `summing_squares(8) # -> 2` 16 | 17 | `summing_squares(9) # -> 1` 18 | 19 | `summing_squares(12) # -> 3` 20 | 21 | `summing_squares(1) # -> 1` 22 | 23 | `summing_squares(31) # -> 4` 24 | 25 | `summing_squares(50) # -> 2` 26 | 27 | `summing_squares(68) # -> 2` 28 | 29 | `summing_squares(87) # -> 4` 30 | -------------------------------------------------------------------------------- /arrays_and_strings/pair_product/README.md: -------------------------------------------------------------------------------- 1 | # pair product 2 | 3 | Write a function, `pair_product`, that takes in a list and a target product as arguments. The function should return a tuple containing a pair of indices whose elements multiply to the given target. The indices returned must be unique. 4 | 5 | Be sure to return the indices, not the elements themselves. 6 | 7 | There is guaranteed to be one such pair whose product is the target. 8 | 9 | ## test_00 10 | 11 | `pair_product([3, 2, 5, 4, 1], 8) # -> (1, 3)` 12 | 13 | ## test_01 14 | 15 | `pair_product([3, 2, 5, 4, 1], 10) # -> (1, 2)` 16 | 17 | ## test_02 18 | 19 | `pair_product([4, 7, 9, 2, 5, 1], 5) # -> (4, 5)` 20 | 21 | ## test_03 22 | 23 | `pair_product([4, 7, 9, 2, 5, 1], 35) # -> (1, 4)` 24 | 25 | ## test_04 26 | 27 | `pair_product([3, 2, 5, 4, 1], 10) # -> (1, 2)` 28 | 29 | ## test_05 30 | 31 | `pair_product([4, 6, 8, 2], 16) # -> (2, 3)` 32 | -------------------------------------------------------------------------------- /arrays_and_strings/tests/test_intersection.py: -------------------------------------------------------------------------------- 1 | from intersection.intersection_v1 import intersection as intersection_v1 2 | from intersection.intersection_v2 import intersection as intersection_v2 3 | from intersection.intersection_v3 import intersection as intersection_v3 4 | 5 | test_case = [ 6 | ( 7 | [4,2,1,6], [3,6,9,2,10],[6,2] 8 | ), 9 | ( 10 | [2,4,6], [4,2], [4,2] 11 | ), 12 | ( 13 | [4,2,1], [1,2,4,6], [1,2,4] 14 | ), 15 | ( 16 | [0,1,2], [10,11], [] 17 | ), 18 | ( 19 | [ i for i in range(0, 50000) ], [ i for i in range(0, 50000) ],[i for i in range(0, 50000)] 20 | ) 21 | ] 22 | 23 | def test_intersection(): 24 | for test in test_case: 25 | assert intersection_v1(test[0],test[1]) == test[2] 26 | assert intersection_v2(test[0],test[1]) == test[2] 27 | assert intersection_v3(test[0],test[1]) == test[2] 28 | 29 | -------------------------------------------------------------------------------- /dynamic_programming/word_break/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | You are given a string, `s`, and an array of strings, `word_dict`, representing a dictionary. Your task is to add spaces to `s` to break it up into a sequence of valid words from `word_dict`. We are required to return an array of all possible sequences of words (sentences). The order in which the sentences are listed is not significant. 4 | 5 | > Note: The same dictionary word may be reused multiple times in the segmentation. 6 | 7 | Constraints: 8 | 9 | 1 ≤ s.length ≤ 20 10 | 11 | 1 ≤ word_dict.length ≤ 1000 12 | 13 | 1 ≤ word_dict[i].length ≤ 10 14 | 15 | s and word_dict[i] consist of only lowercase English letters. 16 | 17 | All the strings of word_dict are unique. 18 | 19 | ### **Example** 20 | 21 | s = "catsanddogs" 22 | word_dict = ["cat", "and", "cats", "sand", "dog"] 23 | 24 | Output: ["cats and dog", "cat sand dog"] 25 | -------------------------------------------------------------------------------- /arrays_and_strings/compress/README.md: -------------------------------------------------------------------------------- 1 | ## compress 2 | 3 | Write a function, `compress`, that takes in a string as an argument. The function should return a compressed version of the string where consecutive occurrences of the same characters are compressed into the number of occurrences followed by the character. Single character occurrences should not be changed. 4 | 5 | `'aaa' compresses to '3a' 6 | 7 | 8 | 'cc' compresses to '2c' 9 | 10 | 11 | 't' should remain as 't'` 12 | 13 | You can assume that the input only contains alphabetic characters. 14 | 15 | ## test_00: 16 | 17 | `compress('ccaaatsss')` 18 | 19 | ## test_01: 20 | 21 | `compress('ssssbbz')` 22 | 23 | ## test_02: 24 | 25 | `compress('ppoppppp')` 26 | 27 | ## test_03: 28 | 29 | `compress('nnneeeeeeeeeeeezz')` 30 | 31 | ## test_04: 32 | 33 | `compress('yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')` 34 | -------------------------------------------------------------------------------- /dynamic_programming/min_change/README.md: -------------------------------------------------------------------------------- 1 | # min change 2 | 3 | Write a function `min_change` that takes in an amount and a list of coins. The function should return the minimum number of coins required to create the amount. You may use each coin as many times as necessary. 4 | 5 | If it is not possible to create the amount, then return -1. 6 | 7 | ```python 8 | min_change(8, [1, 5, 4, 12]) # -> 2, because 4+4 is the minimum coins possible 9 | ``` 10 | 11 | ```python 12 | min_change(13, [1, 9, 5, 14, 30]) # -> 5 13 | ``` 14 | 15 | ```python 16 | min_change(23, [2, 5, 7]) # -> 4 17 | ``` 18 | 19 | ```python 20 | min_change(102, [1, 5, 10, 25]) # -> 6 21 | ``` 22 | 23 | ```python 24 | min_change(200, [1, 5, 10, 25]) # -> 8 25 | ``` 26 | 27 | ```python 28 | min_change(2017, [4, 2, 10]) # -> -1 29 | ``` 30 | 31 | ```python 32 | min_change(271, [10, 8, 265, 24]) # -> -1 33 | ``` 34 | 35 | ```python 36 | min_change(0, [4, 2, 10]) # -> 0 37 | ``` 38 | 39 | ```python 40 | min_change(0, []) # -> 0 41 | ``` 42 | -------------------------------------------------------------------------------- /linked_lists/tests/test_sum_list.py: -------------------------------------------------------------------------------- 1 | from sum_list.sum_list_v1 import sum_list as sum_list_v1 2 | from sum_list.sum_list_v2 import sum_list as sum_list_v2 3 | from traversal.traversal_v1 import Node 4 | 5 | test_case = [19, 42, 100, 0] 6 | 7 | def test_sum_list_a(): 8 | a = Node(2) 9 | b = Node(8) 10 | c = Node(3) 11 | d = Node(-1) 12 | e = Node(7) 13 | 14 | a.next = b 15 | b.next = c 16 | c.next = d 17 | d.next = e 18 | 19 | assert sum_list_v1(a)==test_case[0] 20 | assert sum_list_v2(a)==test_case[0] 21 | 22 | def test_sum_list_x(): 23 | x = Node(38) 24 | y = Node(4) 25 | 26 | x.next = y 27 | 28 | assert sum_list_v1(x)==test_case[1] 29 | assert sum_list_v2(x)==test_case[1] 30 | 31 | def test_sum_list_z(): 32 | z = Node(100) 33 | assert sum_list_v1(z)==test_case[2] 34 | assert sum_list_v2(z)==test_case[2] 35 | 36 | def test_sum_list_none(): 37 | assert sum_list_v1(None)==test_case[3] 38 | assert sum_list_v2(None)==test_case[3] 39 | -------------------------------------------------------------------------------- /arrays_and_strings/container_with_most_water/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `ith` line are `(i, 0)` and `(i, height[i])`. 4 | 5 | Find two lines that together with the x-axis form a container, such that the container contains the most water. 6 | 7 | Return the maximum amount of water a container can store. 8 | 9 | Notice that you may not slant the container. 10 | 11 | ### Example 1 12 | Input: height = [1,8,6,2,5,4,8,3,7] 13 | Output: 49 14 | 15 | Explanation: The vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. 16 | In this case, the max area of water the container can contain is 49. Derived from (1, height[1]) and (8, height[8]) which gives us the heights; h1 = 8, h2 = 7. 17 | To get the width: 8 - 1 = 7 18 | To get height = min(8, 7) = 7 19 | To get area = height * width = 7 * 7 = 49 20 | 21 | ### Example 2 22 | Input: height = [1,1] 23 | Output: 1 -------------------------------------------------------------------------------- /arrays_and_strings/anagrams/README.md: -------------------------------------------------------------------------------- 1 | ## anagram 2 | 3 | Write a function, `anagram`, that takes in two strings as arguments. The function should return a boolean indicating whether or not the strings are anagram. Anagram are strings that contain the same characters, but in any order. 4 | 5 | ## test_00: 6 | 7 | `anagrams('restful', 'fluster') # -> True` 8 | 9 | ## test_01: 10 | 11 | `anagrams('cats', 'tocs') # -> False` 12 | 13 | ## test_02: 14 | 15 | `anagrams('monkeyswrite', 'newyorktimes') # -> True` 16 | 17 | ## test_03: 18 | 19 | `anagrams('paper', 'reapa') # -> False` 20 | 21 | ## test_04: 22 | 23 | `anagrams('elbow', 'below') # -> True` 24 | 25 | ## test_05: 26 | 27 | `anagrams('tax', 'taxi') # -> False` 28 | 29 | ## test_06: 30 | 31 | `anagrams('taxi', 'tax') # -> False` 32 | 33 | ## test_07: 34 | 35 | `anagrams('night', 'thing') # -> True` 36 | 37 | ## test_08: 38 | 39 | `anagrams('abbc', 'aabc') # -> False` 40 | 41 | ## test_09: 42 | 43 | `anagrams('po', 'popp') # -> false` 44 | 45 | ## test_10: 46 | 47 | `anagrams('pp', 'oo') # -> false` 48 | -------------------------------------------------------------------------------- /arrays_and_strings/find_duplicate_number/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given an array of positive numbers, `nums`, such that the values lie in the range `[1,n]`, inclusive, and that there are `n+1` numbers in the array, find and return the duplicate number present in `nums`. There is only one repeated number in `nums`. 4 | 5 | **Note**: You cannot modify the given array nums. You have to solve the problem using only constant extra space. 6 | 7 | Your task is to determine if `nums` has a cycle. Return TRUE if there is a cycle. Otherwise return FALSE. 8 | 9 | ### Constraints 10 | 11 | > 1 ≤ n ≤ 103 12 | 13 | > nums.length = n+1 14 | 15 | > 1 ≤ nums[i] ≤ n 16 | 17 | All the integers in `nums` are unique, except for one integer that will appear more than once. 18 | 19 | 20 | ### Example 1 21 | Input: nums = [1, 3, 3, 4, 2, 5] 22 | Output: 3 23 | 24 | 25 | ### Example 2 26 | Input: nums = [1, 5, 3, 4, 2, 5] 27 | Output: 5 28 | 29 | ### Example 3 30 | Input: nums = [1, 2, 3, 4, 5, 6, 6, 7] 31 | Output: 6 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /dynamic_programming/max_palin_subsequence/README.md: -------------------------------------------------------------------------------- 1 | # max palin subsequence 2 | 3 | Write a function, `max_palin_subsequence`, that takes in a string as an argument. The function should return the length of the longest subsequence of the string that is also a palindrome. 4 | 5 | A subsequence of a string can be created by deleting any characters of the string, while maintaining the relative order of characters. 6 | 7 | ```python 8 | max_palin_subsequence("luwxult") # -> 5 9 | ``` 10 | 11 | ```python 12 | max_palin_subsequence("xyzaxxzy") # -> 6 13 | ``` 14 | 15 | ```python 16 | max_palin_subsequence("lol") # -> 3 17 | ``` 18 | 19 | ```python 20 | max_palin_subsequence("boabcdefop") # -> 3 21 | ``` 22 | 23 | ```python 24 | max_palin_subsequence("z") # -> 1 25 | ``` 26 | 27 | ```python 28 | max_palin_subsequence("chartreusepugvicefree") # -> 7 29 | ``` 30 | 31 | ```python 32 | max_palin_subsequence("qwueoiuahsdjnweuueueunasdnmnqweuzqwerty") # -> 15 33 | ``` 34 | 35 | ```python 36 | max_palin_subsequence("enamelpinportlandtildecoldpressedironyflannelsemioticsedisonbulbfashionaxe") # -> 31 37 | ``` 38 | -------------------------------------------------------------------------------- /dynamic_programming/counting_change/README.md: -------------------------------------------------------------------------------- 1 | # counting change 2 | 3 | Write a function, counting_change, that takes in an amount and a list of coins. The function should return the number of different ways it is possible to make change for the given amount using the coins. 4 | 5 | You may reuse a coin as many times as necessary. 6 | 7 | For example, 8 | 9 | ``` 10 | ounting_change(4, [1,2,3]) -> 4 11 | 12 | There are four different ways to make an amount of 4: 13 | 14 | 1. 1 + 1 + 1 + 1 15 | 2. 1 + 1 + 2 16 | 3. 1 + 3 17 | 4. 2 + 2 18 | 19 | ``` 20 | 21 | ```python 22 | counting_change(4, [1, 2, 3]) # -> 4 23 | ``` 24 | 25 | ```python 26 | counting_change(8, [1, 2, 3]) # -> 10 27 | ``` 28 | 29 | ```python 30 | counting_change(24, [5, 7, 3]) # -> 5 31 | ``` 32 | 33 | ```python 34 | counting_change(13, [2, 6, 12, 10]) # -> 0 35 | ``` 36 | 37 | ```python 38 | counting_change(512, [1, 5, 10, 25]) # -> 20119 39 | ``` 40 | 41 | ```python 42 | counting_change(1000, [1, 5, 10, 25]) # -> 142511 43 | ``` 44 | 45 | ```python 46 | counting_change(240, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # -> 1525987916 47 | ``` 48 | -------------------------------------------------------------------------------- /binary_tree/tree_sum/README.md: -------------------------------------------------------------------------------- 1 | # tree sum 2 | 3 | Write a function, `tree_sum`, that takes in the root of a binary tree that contains number values. The function should return the total sum of all values in the tree. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node(3) 9 | b = Node(11) 10 | c = Node(4) 11 | d = Node(4) 12 | e = Node(-2) 13 | f = Node(1) 14 | 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # 3 22 | # / \ 23 | # 11 4 24 | # / \ \ 25 | # 4 -2 1 26 | 27 | tree_sum(a) # -> 21 28 | ``` 29 | 30 | ## test_01: 31 | 32 | ```python 33 | a = Node(1) 34 | b = Node(6) 35 | c = Node(0) 36 | d = Node(3) 37 | e = Node(-6) 38 | f = Node(2) 39 | g = Node(2) 40 | h = Node(2) 41 | 42 | a.left = b 43 | a.right = c 44 | b.left = d 45 | b.right = e 46 | c.right = f 47 | e.left = g 48 | f.right = h 49 | 50 | # 1 51 | # / \ 52 | # 6 0 53 | # / \ \ 54 | # 3 -6 2 55 | # / \ 56 | # 2 2 57 | 58 | tree_sum(a) # -> 10 59 | ``` 60 | 61 | ## test_02: 62 | 63 | ```python 64 | tree_sum(None) # -> 0 65 | ``` 66 | -------------------------------------------------------------------------------- /dynamic_programming/sum_possible/README.md: -------------------------------------------------------------------------------- 1 | # sum possible 2 | 3 | Write a function `sum_possible` that takes in an amount and a list of positive numbers. The function should return a boolean indicating whether or not it is possible to create the amount by summing numbers of the list. You may reuse numbers of the list as many times as necessary. 4 | 5 | You may assume that the target amount is non-negative. 6 | 7 | ```python 8 | sum_possible(8, [5, 12, 4]) # -> True, 4 + 4 9 | ``` 10 | 11 | ```python 12 | sum_possible(15, [6, 2, 10, 19]) # -> False 13 | ``` 14 | 15 | ```python 16 | sum_possible(13, [6, 2, 1]) # -> True 17 | ``` 18 | 19 | ```python 20 | sum_possible(103, [6, 20, 1]) # -> True 21 | ``` 22 | 23 | ```python 24 | sum_possible(12, []) # -> False 25 | ``` 26 | 27 | ```python 28 | sum_possible(12, [12]) # -> True 29 | ``` 30 | 31 | ```python 32 | sum_possible(0, []) # -> True 33 | ``` 34 | 35 | ```python 36 | sum_possible(271, [10, 8, 265, 24]) # -> False 37 | ``` 38 | 39 | ```python 40 | sum_possible(2017, [4, 2, 10]) # -> False 41 | ``` 42 | 43 | ```python 44 | sum_possible(13, [3, 5]) # -> true 45 | ``` 46 | -------------------------------------------------------------------------------- /dynamic_programming/can_concat/README.md: -------------------------------------------------------------------------------- 1 | # can concat 2 | 3 | Write a function, `can_concat`, that takes in a string and a list of words as arguments. The function should return boolean indicating whether or not it is possible to concatenate words of the list together to form the string. 4 | 5 | You may reuse words of the list as many times as needed. 6 | 7 | ```python 8 | can_concat("oneisnone", ["one", "none", "is"]) # -> True 9 | ``` 10 | 11 | ```python 12 | can_concat("oneisnone", ["on", "e", "is"]) # -> False 13 | ``` 14 | 15 | ```python 16 | can_concat("oneisnone", ["on", "e", "is", "n"]) # -> True 17 | ``` 18 | 19 | ```python 20 | can_concat("foodisgood", ["is", "g", "ood", "f"]) # -> True 21 | ``` 22 | 23 | ```python 24 | can_concat("santahat", ["santah", "hat"]) # -> False 25 | ``` 26 | 27 | ```python 28 | can_concat("santahat", ["santah", "san", "hat", "tahat"]) # -> True 29 | ``` 30 | 31 | ```python 32 | can_concat("rrrrrrrrrrrrrrrrrrrrrrrrrrx", ["r", "rr", "rrr", "rrrr", "rrrrr", "rrrrrr"]) # -> False 33 | ``` 34 | 35 | ```python 36 | can_concat("fooisgood", ["foo", "is", "g", "ood", "f"]) # -> True 37 | ``` 38 | -------------------------------------------------------------------------------- /introduction/is_prime/README.md: -------------------------------------------------------------------------------- 1 | ## is-prime 2 | 3 | Write a function, `is_prime`, that takes in a number as an argument. The function should return a boolean indicating whether or not the given number is prime. 4 | 5 | A prime number is a number that is only divisible by two distinct numbers: 1 and itself. 6 | 7 | For example, 7 is a prime because it is only divisible by 1 and 7. For example, 6 is not a prime because it is divisible by 1, 2, 3, and 6. 8 | 9 | You can assume that the input number is a positive integer. 10 | 11 | ## test_00: 12 | 13 | `is_prime(2)` 14 | 15 | ## test_01: 16 | 17 | `is_prime(3)` 18 | 19 | ## test_02: 20 | 21 | `is_prime(4)` 22 | 23 | ## test_03: 24 | 25 | `is_prime(5)` 26 | 27 | ## test_04: 28 | 29 | `is_prime(6)` 30 | 31 | ## test_05: 32 | 33 | `is_prime(7)` 34 | 35 | ## test_06: 36 | 37 | `is_prime(8)` 38 | 39 | ## test_07: 40 | 41 | `is_prime(25)` 42 | 43 | ## test_08: 44 | 45 | `is_prime(31)` 46 | 47 | ## test_09: 48 | 49 | `is_prime(2017)` 50 | 51 | ## test_10: 52 | 53 | `is_prime(2048)` 54 | 55 | ## test_11: 56 | 57 | `is_prime(1)` 58 | 59 | ## test_12: 60 | 61 | `is_prime(713)` 62 | -------------------------------------------------------------------------------- /graphs/largest_component/README.md: -------------------------------------------------------------------------------- 1 | # largest component 2 | 3 | Write a function, `largest_component`, that takes in the adjacency list of an undirected graph. The function should return the size of the largest connected component in the graph. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | largest_component({ 9 | 0: [8, 1, 5], 10 | 1: [0], 11 | 5: [0, 8], 12 | 8: [0, 5], 13 | 2: [3, 4], 14 | 3: [2, 4], 15 | 4: [3, 2] 16 | }) # -> 4 17 | ``` 18 | 19 | ## test_01: 20 | 21 | ```python 22 | largest_component({ 23 | 1: [2], 24 | 2: [1,8], 25 | 6: [7], 26 | 9: [8], 27 | 7: [6, 8], 28 | 8: [9, 7, 2] 29 | }) # -> 6 30 | ``` 31 | 32 | ## test_02: 33 | 34 | ```python 35 | largest_component({ 36 | 3: [], 37 | 4: [6], 38 | 6: [4, 5, 7, 8], 39 | 8: [6], 40 | 7: [6], 41 | 5: [6], 42 | 1: [2], 43 | 2: [1] 44 | }) # -> 5 45 | ``` 46 | 47 | ## test_03: 48 | 49 | ```python 50 | largest_component({}) # -> 0 51 | ``` 52 | 53 | ## test_04: 54 | 55 | ```python 56 | largest_component({ 57 | 0: [4,7], 58 | 1: [], 59 | 2: [], 60 | 3: [6], 61 | 4: [0], 62 | 6: [3], 63 | 7: [0], 64 | 8: [] 65 | }) # -> 3 66 | ``` 67 | -------------------------------------------------------------------------------- /dynamic_programming/quickest_concat/README.md: -------------------------------------------------------------------------------- 1 | # quickest concat 2 | 3 | Write a function, `quickest_concat`, that takes in a string and a list of words as arguments. The function should return the minimum number of words needed to build the string by concatenating words of the list. 4 | 5 | You may use words of the list as many times as needed. 6 | 7 | ```python 8 | quickest_concat('caution', ['ca', 'ion', 'caut', 'ut']) # -> 2 9 | ``` 10 | 11 | ```python 12 | quickest_concat('caution', ['ion', 'caut', 'caution']) # -> 1 13 | ``` 14 | 15 | ```python 16 | quickest_concat('respondorreact', ['re', 'or', 'spond', 'act', 'respond']) # -> 4 17 | ``` 18 | 19 | ```python 20 | quickest_concat('simchacindy', ['sim', 'simcha', 'acindy', 'ch']) # -> 3 21 | ``` 22 | 23 | ```python 24 | quickest_concat('simchacindy', ['sim', 'simcha', 'acindy']) # -> -1 25 | ``` 26 | 27 | ```python 28 | quickest_concat('uuuuuu', ['u', 'uu', 'uuu', 'uuuu']) # -> 2 29 | ``` 30 | 31 | ```python 32 | quickest_concat('rongbetty', ['wrong', 'bet']) # -> -1 33 | ``` 34 | 35 | ```python 36 | quickest_concat('uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu', ['u', 'uu', 'uuu', 'uuuu', 'uuuuu']) # -> 7 37 | ``` 38 | -------------------------------------------------------------------------------- /stacks/befitting_brackets/README.md: -------------------------------------------------------------------------------- 1 | # befitting brackets 2 | 3 | Write a function, `befitting_brackets`, that takes in a string as an argument. The function should return a boolean indicating whether or not the string contains correctly matched brackets. 4 | 5 | You may assume the string contains only characters: ( ) [ ] { } 6 | 7 | ## test_00 8 | 9 | ```python 10 | befitting_brackets('(){}[](())') # -> True 11 | ``` 12 | 13 | ## test_01 14 | 15 | ```python 16 | befitting_brackets('({[]})') # -> True 17 | ``` 18 | 19 | ## test_02 20 | 21 | ```python 22 | befitting_brackets('[][}') # -> False 23 | ``` 24 | 25 | ## test_03 26 | 27 | ```python 28 | befitting_brackets('{[]}({}') # -> False 29 | ``` 30 | 31 | ## test_04 32 | 33 | ```python 34 | befitting_brackets('[]{}(}[]') # -> False 35 | ``` 36 | 37 | ## test_05 38 | 39 | ```python 40 | befitting_brackets('[]{}()[]') # -> True 41 | ``` 42 | 43 | ## test_06 44 | 45 | ```python 46 | befitting_brackets(']{}') # -> False 47 | ``` 48 | 49 | ## test_07 50 | 51 | ```python 52 | befitting_brackets('') # -> True 53 | ``` 54 | 55 | ## test_08 56 | 57 | ```python 58 | befitting_brackets("{[(}])") # -> False 59 | ``` 60 | -------------------------------------------------------------------------------- /stacks/paired_parentheses/README.md: -------------------------------------------------------------------------------- 1 | # paired parentheses 2 | 3 | Write a function, `paired_parentheses`, that takes in a string as an argument. The function should return a boolean indicating whether or not the string has well-formed parentheses. 4 | 5 | You may assume the string contains **only** alphabetic characters, '(', or ')'. 6 | 7 | ## test_00 8 | 9 | ```python 10 | paired_parentheses("(david)((abby))") # -> True 11 | ``` 12 | 13 | ## test_01 14 | 15 | ```python 16 | paired_parentheses("()rose(jeff") # -> False 17 | ``` 18 | 19 | ## test_02 20 | 21 | ```python 22 | paired_parentheses(")(") # -> False 23 | ``` 24 | 25 | ## test_03 26 | 27 | ```python 28 | paired_parentheses("()") # -> True 29 | ``` 30 | 31 | ## test_04 32 | 33 | ```python 34 | paired_parentheses("(((potato())))") # -> True 35 | ``` 36 | 37 | ## test_05 38 | 39 | ```python 40 | paired_parentheses("(())(water)()") # -> True 41 | ``` 42 | 43 | ## test_06 44 | 45 | ```python 46 | paired_parentheses("(())(water()()") # -> False 47 | ``` 48 | 49 | ## test_07 50 | 51 | ```python 52 | paired_parentheses("") # -> True 53 | ``` 54 | 55 | ## test_08 56 | 57 | ```python 58 | paired_parentheses("))()") # False 59 | ``` 60 | -------------------------------------------------------------------------------- /graphs/connected_components/README.md: -------------------------------------------------------------------------------- 1 | # connected components count 2 | 3 | Write a function, `connected_components_count`, that takes in the adjacency list of an undirected graph. The function should return the number of connected components within the graph. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | connected_components_count({ 9 | 0: [8, 1, 5], 10 | 1: [0], 11 | 5: [0, 8], 12 | 8: [0, 5], 13 | 2: [3, 4], 14 | 3: [2, 4], 15 | 4: [3, 2] 16 | }) # -> 2 17 | ``` 18 | 19 | ## test_01: 20 | 21 | ```python 22 | connected_components_count({ 23 | 1: [2], 24 | 2: [1,8], 25 | 6: [7], 26 | 9: [8], 27 | 7: [6, 8], 28 | 8: [9, 7, 2] 29 | }) # -> 1 30 | ``` 31 | 32 | ## test_02: 33 | 34 | ```python 35 | connected_components_count({ 36 | 3: [], 37 | 4: [6], 38 | 6: [4, 5, 7, 8], 39 | 8: [6], 40 | 7: [6], 41 | 5: [6], 42 | 1: [2], 43 | 2: [1] 44 | }) # -> 3 45 | ``` 46 | 47 | ## test_03: 48 | 49 | ```python 50 | connected_components_count({}) # -> 0 51 | ``` 52 | 53 | ## test_04: 54 | 55 | ```python 56 | connected_components_count({ 57 | 0: [4,7], 58 | 1: [], 59 | 2: [], 60 | 3: [6], 61 | 4: [0], 62 | 6: [3], 63 | 7: [0], 64 | 8: [] 65 | }) # -> 5 66 | ``` 67 | -------------------------------------------------------------------------------- /arrays_and_strings/five_sort/README.md: -------------------------------------------------------------------------------- 1 | # five sort 2 | 3 | Write a function, `five_sort`, that takes in a list of numbers as an argument. The function should rearrange elements of the list such that all 5s appear at the end. Your function should perform this operation in-place by mutating the original list. The function should return the list. 4 | 5 | Elements that are not 5 can appear in any order in the output, as long as all 5s are at the end of the list. 6 | 7 | ## test_00 8 | 9 | `five_sort([12, 5, 1, 5, 12, 7]) # -> [12, 7, 1, 12, 5, 5]` 10 | 11 | ## test_01 12 | 13 | `five_sort([5, 2, 5, 6, 5, 1, 10, 2, 5, 5]) # -> [2, 2, 10, 6, 1, 5, 5, 5, 5, 5]` 14 | 15 | ## test_02 16 | 17 | `five_sort([5, 5, 5, 1, 1, 1, 4]) # -> [4, 1, 1, 1, 5, 5, 5]` 18 | 19 | ## test_03 20 | 21 | `five_sort([5, 5, 6, 5, 5, 5, 5]) # -> [6, 5, 5, 5, 5, 5, 5]` 22 | 23 | ## test_04 24 | 25 | `five_sort([5, 1, 2, 5, 5, 3, 2, 5, 1, 5, 5, 5, 4, 5]) # -> [4, 1, 2, 1, 2, 3, 5, 5, 5, 5, 5, 5, 5, 5]` 26 | 27 | ## test_05 28 | 29 | `fours = [4] * 20000` 30 | 31 | `fives = [5] * 20000` 32 | 33 | `nums = fours + fives` 34 | 35 | `five_sort(nums)` 36 | 37 | `twenty-thousand 4s followed by twenty-thousand 5s` 38 | 39 | `# -> [4, 4, 4, 4, ..., 5, 5, 5, 5]` 40 | -------------------------------------------------------------------------------- /arrays_and_strings/three_sum/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | Given an integer array `nums`, return all the triplets `[nums[i]`, `nums[j]`, `nums[k]` such that 4 | 5 | i != j, i != k, and j != k, and 6 | nums[i] + nums[j] + nums[k] == 0. 7 | 8 | ### Constraints: 9 | 10 | > 3 <= nums.length <= 3000 11 | 12 | > -105 <= nums[i] <= 105 13 | 14 | Notice that the solution set must **not** contain duplicate triplets. 15 | 16 | ### Example 1: 17 | 18 | Input: nums = [-1,0,1,2,-1,-4] 19 | Output: [[-1,-1,2],[-1,0,1]] 20 | 21 | Explanation: 22 | nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. 23 | nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. 24 | nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. 25 | 26 | The distinct triplets are [-1,0,1] and [-1,-1,2]. 27 | Notice that the order of the output and the order of the triplets does not matter. 28 | 29 | ### Example 2: 30 | 31 | Input: nums = [0,1,1] 32 | Output: [] 33 | Explanation: The only possible triplet does not sum up to 0. 34 | 35 | ### Example 3: 36 | 37 | Input: nums = [0,0,0] 38 | Output: [[0,0,0]] 39 | Explanation: The only possible triplet sums up to 0. 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /graphs/island_count/README.md: -------------------------------------------------------------------------------- 1 | # island count 2 | 3 | Write a function, `island_count`, that takes in a grid containing Ws and Ls. W represents water and L represents land. The function should return the number of islands on the grid. An island is a vertically or horizontally connected region of land. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | 9 | grid = [ 10 | ['W', 'L', 'W', 'W', 'W'], 11 | ['W', 'L', 'W', 'W', 'W'], 12 | ['W', 'W', 'W', 'L', 'W'], 13 | ['W', 'W', 'L', 'L', 'W'], 14 | ['L', 'W', 'W', 'L', 'L'], 15 | ['L', 'L', 'W', 'W', 'W'], 16 | ] 17 | 18 | island_count(grid) # -> 3 19 | ``` 20 | 21 | ## test_01: 22 | 23 | ```python 24 | 25 | grid = [ 26 | ['L', 'W', 'W', 'L', 'W'], 27 | ['L', 'W', 'W', 'L', 'L'], 28 | ['W', 'L', 'W', 'L', 'W'], 29 | ['W', 'W', 'W', 'W', 'W'], 30 | ['W', 'W', 'L', 'L', 'L'], 31 | ] 32 | 33 | island_count(grid) # -> 4 34 | ``` 35 | 36 | ## test_02: 37 | 38 | ```python 39 | 40 | grid = [ 41 | ['L', 'L', 'L'], 42 | ['L', 'L', 'L'], 43 | ['L', 'L', 'L'], 44 | ] 45 | 46 | island_count(grid) # -> 1 47 | ``` 48 | 49 | ## test_03: 50 | 51 | ```python 52 | 53 | grid = [ 54 | ['W', 'W'], 55 | ['W', 'W'], 56 | ['W', 'W'], 57 | ] 58 | 59 | island_count(grid) # -> 0 60 | ``` 61 | -------------------------------------------------------------------------------- /graphs/has_cycle/README.md: -------------------------------------------------------------------------------- 1 | # has cycle 2 | 3 | Write a function, `has_cycle`, that takes in an object representing the adjacency list of a directed graph. The function should return a boolean indicating whether or not the graph contains a cycle. 4 | 5 | ## test_00 6 | 7 | ```python 8 | has_cycle({ 9 | "a": ["b"], 10 | "b": ["c"], 11 | "c": ["a"], 12 | }) # -> True 13 | ``` 14 | 15 | ## test_01 16 | 17 | ```python 18 | has_cycle({ 19 | "a": ["b", "c"], 20 | "b": ["c"], 21 | "c": ["d"], 22 | "d": [], 23 | }) # -> False 24 | ``` 25 | 26 | ## test_02 27 | 28 | ```python 29 | has_cycle({ 30 | "a": ["b", "c"], 31 | "b": [], 32 | "c": [], 33 | "e": ["f"], 34 | "f": ["e"], 35 | }) # -> True 36 | ``` 37 | 38 | ## test_03 39 | 40 | ```python 41 | has_cycle({ 42 | "q": ["r", "s"], 43 | "r": ["t", "u"], 44 | "s": [], 45 | "t": [], 46 | "u": [], 47 | "v": ["w"], 48 | "w": [], 49 | "x": ["w"], 50 | }) # -> False 51 | ``` 52 | 53 | ## test_04 54 | 55 | ```python 56 | has_cycle({ 57 | "a": ["b"], 58 | "b": ["c"], 59 | "c": ["a"], 60 | "g": [], 61 | }) # -> True 62 | ``` 63 | 64 | ## test_05 65 | 66 | ```python 67 | has_cycle({ 68 | "a": ["b"], 69 | "b": ["c"], 70 | "c": ["d"], 71 | "d": ["b"], 72 | }) # -> True 73 | ``` 74 | -------------------------------------------------------------------------------- /linked_lists/tests/test_traversal.py: -------------------------------------------------------------------------------- 1 | from traversal.traversal_v1 import traverse as traversal_v1, Node 2 | # from traversal.traversal_v2 import traverse as traversal_v2 #NB:refer to why traversal_v2.py isn't the best solution. try running the tests with this and you'll see. 3 | from linked_lists.traversal.traversal_v2_main import node_values as traversal_v2 4 | 5 | 6 | test_case = [ 7 | [ 'a', 'b', 'c', 'd' ], 8 | [ 'x', 'y' ], 9 | ['q'], 10 | [] 11 | ] 12 | 13 | def test_traversal_a(): 14 | a = Node("a") 15 | b = Node("b") 16 | c = Node("c") 17 | d = Node("d") 18 | a.next = b 19 | b.next = c 20 | c.next = d 21 | 22 | assert(traversal_v2(a) == test_case[0]) 23 | assert(traversal_v1(a) == test_case[0]) 24 | 25 | def test_traversal_x(): 26 | 27 | x = Node("x") 28 | y = Node("y") 29 | x.next = y 30 | 31 | 32 | assert(traversal_v2(x) == test_case[1]) 33 | assert(traversal_v1(x) == test_case[1]) 34 | 35 | def test_traversal_q(): 36 | 37 | q = Node("q") 38 | assert(traversal_v2(q) == test_case[2]) 39 | assert(traversal_v1(q) == test_case[2]) 40 | 41 | def test_traversal_none(): 42 | 43 | assert(traversal_v2(None) == test_case[3]) 44 | assert(traversal_v1(None) == test_case[3]) -------------------------------------------------------------------------------- /graphs/longest_path/README.md: -------------------------------------------------------------------------------- 1 | # longest path 2 | 3 | Write a function, `longest_path`, that takes in an adjacency list for a **directed acyclic graph**. The function should return the length of the longest path within the graph. A path may start and end at any two nodes. The length of a path is considered the number of edges in the path, not the number of nodes. 4 | 5 | ## test 00 6 | 7 | ```python 8 | 9 | graph = { 10 | 'a': ['c', 'b'], 11 | 'b': ['c'], 12 | 'c': [] 13 | } 14 | 15 | longest_path(graph) # -> 2 16 | ``` 17 | 18 | ## test 01 19 | 20 | ```python 21 | 22 | graph = { 23 | 'a': ['c', 'b'], 24 | 'b': ['c'], 25 | 'c': [], 26 | 'q': ['r'], 27 | 'r': ['s', 'u', 't'], 28 | 's': ['t'], 29 | 't': ['u'], 30 | 'u': [] 31 | } 32 | 33 | longest_path(graph) # -> 4 34 | ``` 35 | 36 | ## test 02 37 | 38 | ```python 39 | 40 | graph = { 41 | 'h': ['i', 'j', 'k'], 42 | 'g': ['h'], 43 | 'i': [], 44 | 'j': [], 45 | 'k': [], 46 | 'x': ['y'], 47 | 'y': [] 48 | } 49 | 50 | longest_path(graph) # -> 2 51 | ``` 52 | 53 | ## test 03 54 | 55 | ```python 56 | 57 | graph = { 58 | 'a': ['b'], 59 | 'b': ['c'], 60 | 'c': [], 61 | 'e': ['f'], 62 | 'f': ['g'], 63 | 'g': ['h'], 64 | 'h': [] 65 | } 66 | 67 | longest_path(graph) # -> 3 68 | ``` 69 | -------------------------------------------------------------------------------- /dynamic_programming/01_knapsack/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | You are given `n` items whose weights and values are known, as well as a knapsack to carry these items. The knapsack cannot carry more than a certain maximum weight, known as its `capacity`. 4 | 5 | You need to maximize the total value of the items in your knapsack, while ensuring that the sum of the weights of the selected items does not exceed the capacity of the knapsack. 6 | 7 | If there is no combination of weights whose sum is within the capacity constraint, return 0. 8 | 9 | > 10 | 11 | Notes: 12 | 13 | An item may not be broken up to fit into the knapsack, i.e., 14 | an item either goes into the knapsack in its entirety or not at all. 15 | We may not add an item more than once to the knapsack. 16 | 17 | Constraints: 18 | 19 | 1 ≤ capacity ≤ 1000 20 | 1 ≤ values.length ≤ 500 21 | weights.length == values.length 22 | 1 ≤ values[i] ≤ 1000 23 | 1 ≤ weights[i] ≤ capacity 24 | 25 | ### **Example** 26 | 27 | capacity = 30 28 | weights = [10,20,30] 29 | values = [22,33,44] 30 | 31 | Output = 55 32 | Using weights 20 and 10 to reach max capacity with values of 22 and 33 to give us max profit of 55 33 | Using the weight of 30 would give us a max profit of 44 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /graphs/minimum_island/README.md: -------------------------------------------------------------------------------- 1 | # minimum island 2 | 3 | Write a function, `minimum_island`, that takes in a grid containing Ws and Ls. W represents water and L represents land. The function should return the size of the smallest island. An island is a vertically or horizontally connected region of land. 4 | 5 | You may assume that the grid contains at least one island. 6 | 7 | ## test_00: 8 | 9 | ```python 10 | 11 | grid = [ 12 | ['W', 'L', 'W', 'W', 'W'], 13 | ['W', 'L', 'W', 'W', 'W'], 14 | ['W', 'W', 'W', 'L', 'W'], 15 | ['W', 'W', 'L', 'L', 'W'], 16 | ['L', 'W', 'W', 'L', 'L'], 17 | ['L', 'L', 'W', 'W', 'W'], 18 | ] 19 | 20 | minimum_island(grid) # -> 2 21 | ``` 22 | 23 | ## test_01: 24 | 25 | ```python 26 | 27 | grid = [ 28 | ['L', 'W', 'W', 'L', 'W'], 29 | ['L', 'W', 'W', 'L', 'L'], 30 | ['W', 'L', 'W', 'L', 'W'], 31 | ['W', 'W', 'W', 'W', 'W'], 32 | ['W', 'W', 'L', 'L', 'L'], 33 | ] 34 | 35 | minimum_island(grid) # -> 1 36 | ``` 37 | 38 | ## test_02: 39 | 40 | ```python 41 | 42 | grid = [ 43 | ['L', 'L', 'L'], 44 | ['L', 'L', 'L'], 45 | ['L', 'L', 'L'], 46 | ] 47 | 48 | minimum_island(grid) # -> 9 49 | ``` 50 | 51 | ## test_03: 52 | 53 | ```python 54 | 55 | grid = [ 56 | ['W', 'W'], 57 | ['L', 'L'], 58 | ['W', 'W'], 59 | ['W', 'L'] 60 | ] 61 | 62 | minimum_island(grid) # -> 1 63 | ``` 64 | -------------------------------------------------------------------------------- /stacks/nesting_score/README.md: -------------------------------------------------------------------------------- 1 | # nesting score 2 | 3 | Write a function, `nesting_score`, that takes in a string of brackets as an argument. The function should return the score of the string according to the following rules: 4 | 5 | - [] is worth 1 point 6 | - XY is worth m + n points where X, Y are substrings of well-formed brackets and m, n are their respective scores 7 | - [S] is worth 2 * k points where S is a substring of well-formed brackets and k is the score of that substring 8 | 9 | You may assume that the input only contains well-formed square brackets. 10 | 11 | ## test_00 12 | 13 | ```python 14 | nesting_score("[]") # -> 1 15 | ``` 16 | 17 | ## test_01 18 | 19 | ```python 20 | nesting_score("[][][]") # -> 3 21 | ``` 22 | 23 | ## test_02 24 | 25 | ```python 26 | nesting_score("[[]]") # -> 2 27 | ``` 28 | 29 | ## test_03 30 | 31 | ```python 32 | nesting_score("[[][]]") # -> 4 33 | ``` 34 | 35 | ## test_04 36 | 37 | ```python 38 | nesting_score("[[][][]]") # -> 6 39 | ``` 40 | 41 | ## test_05 42 | 43 | ```python 44 | nesting_score("[[][]][]") # -> 5 45 | ``` 46 | 47 | ## test_06 48 | 49 | ```python 50 | nesting_score("[][[][]][[]]") # -> 7 51 | ``` 52 | 53 | ## test_07 54 | 55 | ```python 56 | nesting_score("[[[[[[[][]]]]]]][]") # -> 129 57 | ``` 58 | 59 | ## test_08 60 | 61 | ```python 62 | nesting_score("") # -> 0 63 | ``` 64 | -------------------------------------------------------------------------------- /linked_lists/is_univalue_list/README.md: -------------------------------------------------------------------------------- 1 | # is univalue list 2 | 3 | Write a function, `is_univalue_list`, that takes in the head of a linked list as an argument. The function should return a boolean indicating whether or not the linked list contains exactly one unique value. 4 | 5 | You may assume that the input list is non-empty. 6 | 7 | ## test_00: 8 | 9 | ```python 10 | a = Node(7) 11 | b = Node(7) 12 | c = Node(7) 13 | 14 | a.next = b 15 | b.next = c 16 | 17 | # 7 -> 7 -> 7 18 | 19 | is_univalue_list(a) # True 20 | ``` 21 | 22 | ## test_01: 23 | 24 | ```python 25 | a = Node(7) 26 | b = Node(7) 27 | c = Node(4) 28 | 29 | a.next = b 30 | b.next = c 31 | 32 | # 7 -> 7 -> 4 33 | 34 | is_univalue_list(a) # False 35 | ``` 36 | 37 | ## test_02: 38 | 39 | ```python 40 | u = Node(2) 41 | v = Node(2) 42 | w = Node(2) 43 | x = Node(2) 44 | y = Node(2) 45 | 46 | u.next = v 47 | v.next = w 48 | w.next = x 49 | x.next = y 50 | 51 | # 2 -> 2 -> 2 -> 2 -> 2 52 | 53 | is_univalue_list(u) # True 54 | ``` 55 | 56 | ## test_03: 57 | 58 | ```python 59 | u = Node(2) 60 | v = Node(2) 61 | w = Node(3) 62 | x = Node(3) 63 | y = Node(2) 64 | 65 | u.next = v 66 | v.next = w 67 | w.next = x 68 | x.next = y 69 | 70 | # 2 -> 2 -> 3 -> 3 -> 2 71 | 72 | is_univalue_list(u) # False 73 | ``` 74 | 75 | ## test_04: 76 | 77 | ```python 78 | z = Node('z') 79 | 80 | # z 81 | 82 | is_univalue_list(z) # True 83 | ``` 84 | -------------------------------------------------------------------------------- /linked_lists/get_node_value/README.md: -------------------------------------------------------------------------------- 1 | # get node value 2 | 3 | Write a function, `get_node_value`, that takes in the head of a linked list and an index. The function should return the value of the linked list at the specified index. 4 | 5 | If there is no node at the given index, then return None. 6 | ## test_00: 7 | ```python 8 | a = Node("a") 9 | b = Node("b") 10 | c = Node("c") 11 | d = Node("d") 12 | 13 | a.next = b 14 | b.next = c 15 | c.next = d 16 | 17 | # a -> b -> c -> d 18 | 19 | get_node_value(a, 2) # 'c' 20 | ``` 21 | 22 | ## test_01: 23 | ```python 24 | a = Node("a") 25 | b = Node("b") 26 | c = Node("c") 27 | d = Node("d") 28 | 29 | a.next = b 30 | b.next = c 31 | c.next = d 32 | 33 | # a -> b -> c -> d 34 | 35 | get_node_value(a, 3) # 'd' 36 | ``` 37 | 38 | ## test_02: 39 | ```python 40 | a = Node("a") 41 | b = Node("b") 42 | c = Node("c") 43 | d = Node("d") 44 | 45 | a.next = b 46 | b.next = c 47 | c.next = d 48 | 49 | # a -> b -> c -> d 50 | 51 | get_node_value(a, 7) # None 52 | ``` 53 | 54 | ## test_03: 55 | ```python 56 | node1 = Node("banana") 57 | node2 = Node("mango") 58 | 59 | node1.next = node2 60 | 61 | # banana -> mango 62 | 63 | get_node_value(node1, 0) # 'banana' 64 | ``` 65 | 66 | ## test_04: 67 | ```python 68 | node1 = Node("banana") 69 | node2 = Node("mango") 70 | 71 | node1.next = node2 72 | 73 | # banana -> mango 74 | 75 | get_node_value(node1, 1) # 'mango' 76 | ``` 77 | -------------------------------------------------------------------------------- /dynamic_programming/house_robber/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | A professional robber plans to rob some houses along a street. These houses are arranged in a circle, which means that the first and the last house are neighbors. The robber cannot rob adjacent houses because they have security alarms installed. 4 | 5 | Following the constraints mentioned above and given an integer array money representing the amount of money in each house, return the maximum amount the robber can steal without alerting the police. 6 | 7 | ### **Constraints**: 8 | 9 | > 1 ≤ money.length ≤ 103 10 | 11 | > 0 ≤ money[ i ] ≤ 103 12 | 13 | ### **Examples** 14 | Example 1: 15 | Input = [1,5,3] 16 | Output = 5 17 | Since 1 and 3 cannot be selected together, the maximum amount to be robbed is 5. 18 | 19 | Example 2: 20 | Input = [1,2,3,2] 21 | Output = 4 22 | The possible combos are: 23 | 1+3 = 4 24 | 2+2 = 4 25 | Since both combinations result in 4 and the houses have lower amounts if selected alone, the maximum amount to be robbed is 4. 26 | 27 | Example 3: 28 | Input = [1,2,10,2] 29 | Output = 11 30 | The possible combinations are: 31 | 1 + 10 = 11 32 | 2 + 2 = 4 33 | Since 11 is the greatest possible amount, and the houses have lower amounts if selected alone, the maximum amount robbed is 11. 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /graphs/has_path/README.md: -------------------------------------------------------------------------------- 1 | # has path 2 | 3 | Write a function, `has_path`, that takes in a dictionary representing the adjacency list of a **directed acyclic** graph and two nodes (src, dst). The function should return a boolean indicating whether or not there exists a directed path between the source and destination nodes. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | graph = { 9 | 'f': ['g', 'i'], 10 | 'g': ['h'], 11 | 'h': [], 12 | 'i': ['g', 'k'], 13 | 'j': ['i'], 14 | 'k': [] 15 | } 16 | 17 | has_path(graph, 'f', 'k') # True 18 | ``` 19 | 20 | ## test_01: 21 | 22 | ```python 23 | graph = { 24 | 'f': ['g', 'i'], 25 | 'g': ['h'], 26 | 'h': [], 27 | 'i': ['g', 'k'], 28 | 'j': ['i'], 29 | 'k': [] 30 | } 31 | 32 | has_path(graph, 'f', 'j') # False 33 | ``` 34 | 35 | ## test_02: 36 | 37 | ```python 38 | graph = { 39 | 'f': ['g', 'i'], 40 | 'g': ['h'], 41 | 'h': [], 42 | 'i': ['g', 'k'], 43 | 'j': ['i'], 44 | 'k': [] 45 | } 46 | 47 | has_path(graph, 'i', 'h') # True 48 | ``` 49 | 50 | ## test_03: 51 | 52 | ```python 53 | graph = { 54 | 'v': ['x', 'w'], 55 | 'w': [], 56 | 'x': [], 57 | 'y': ['z'], 58 | 'z': [], 59 | } 60 | 61 | has_path(graph, 'v', 'w') # True 62 | ``` 63 | 64 | ## test_04: 65 | 66 | ```python 67 | graph = { 68 | 'v': ['x', 'w'], 69 | 'w': [], 70 | 'x': [], 71 | 'y': ['z'], 72 | 'z': [], 73 | } 74 | 75 | has_path(graph, 'v', 'z') # False 76 | ``` 77 | -------------------------------------------------------------------------------- /binary_tree/how_high/how_high_v3.py: -------------------------------------------------------------------------------- 1 | def how_high(node): 2 | """ 3 | This function calculates the height of a binary tree using a recursive approach. 4 | 5 | Args: 6 | node: The root node of the binary tree. 7 | 8 | Returns: 9 | The height of the binary tree, or -1 if the tree is empty. 10 | """ 11 | 12 | # Base case: Empty tree 13 | if node is None: 14 | return -1 # Empty tree has a height of -1 15 | 16 | # Recursive calls to calculate left and right subtrees' heights 17 | left_height = how_high(node.left) 18 | right_height = how_high(node.right) 19 | 20 | # Return the height of the current node (1 + max(left, right)) 21 | return 1 + max(left_height, right_height) 22 | 23 | 24 | """ 25 | Time Complexity Analysis: 26 | - Each node is visited once: O(n), where n is the number of nodes in the binary tree. 27 | 28 | Space Complexity Analysis: 29 | - Recursive function calls: O(h), where h is the height of the binary tree (worst-case scenario). 30 | - Stack space for recursion: O(h). 31 | Overall: O(h) 32 | 33 | Further Notes: 34 | This algorithm implements a recursive approach to calculate the height of a binary tree. 35 | It works by checking for the base case (empty tree) and then recursively calling itself 36 | on the left and right subtrees to obtain their heights. Finally, it returns 1 (for the current 37 | node) plus the maximum height found from the left and right subtrees. 38 | """ 39 | -------------------------------------------------------------------------------- /linked_lists/linked_list_find/README.md: -------------------------------------------------------------------------------- 1 | # linked list find 2 | 3 | Write a function, `linked_list_find`, that takes in the head of a linked list and a target value. The function should return a boolean indicating whether or not the linked list contains the target. 4 | ## test_00: 5 | ```python 6 | a = Node("a") 7 | b = Node("b") 8 | c = Node("c") 9 | d = Node("d") 10 | 11 | a.next = b 12 | b.next = c 13 | c.next = d 14 | 15 | # a -> b -> c -> d 16 | 17 | linked_list_find(a, "c") # True 18 | ``` 19 | 20 | ## test_01: 21 | ```python 22 | a = Node("a") 23 | b = Node("b") 24 | c = Node("c") 25 | d = Node("d") 26 | 27 | a.next = b 28 | b.next = c 29 | c.next = d 30 | 31 | # a -> b -> c -> d 32 | 33 | linked_list_find(a, "d") # True 34 | ``` 35 | 36 | ## test_02: 37 | ```python 38 | a = Node("a") 39 | b = Node("b") 40 | c = Node("c") 41 | d = Node("d") 42 | 43 | a.next = b 44 | b.next = c 45 | c.next = d 46 | 47 | # a -> b -> c -> d 48 | 49 | linked_list_find(a, "q") # False 50 | ``` 51 | 52 | ## test_03: 53 | ```python 54 | node1 = Node("jason") 55 | node2 = Node("leneli") 56 | 57 | node1.next = node2 58 | 59 | # jason -> leneli 60 | 61 | linked_list_find(node1, "jason") # True 62 | ``` 63 | 64 | ## test_04: 65 | ```python 66 | node1 = Node(42) 67 | 68 | # 42 69 | 70 | linked_list_find(node1, 42) # True 71 | ``` 72 | 73 | ## test_05: 74 | ```python 75 | node1 = Node(42) 76 | 77 | # 42 78 | 79 | linked_list_find(node1, 100) # False 80 | ``` 81 | -------------------------------------------------------------------------------- /dynamic_programming/non_adjacent_sum/README.md: -------------------------------------------------------------------------------- 1 | # non adjacent sum 2 | 3 | Write a function, `non_adjacent_sum`, that takes in a list of numbers as an argument. The function should return the maximum sum of non-adjacent items in the list. There is no limit on how many items can be taken into the sum as long as they are not adjacent. 4 | 5 | For example, given: 6 | 7 | [2, 4, 5, 12, 7] 8 | 9 | The maximum non-adjacent sum is 16, because 4 + 12. 10 | 4 and 12 are not adjacent in the list. 11 | 12 | ## test_0 13 | 14 | ```python 15 | nums = [2, 4, 5, 12, 7] 16 | non_adjacent_sum(nums) # -> 16 17 | ``` 18 | 19 | ## test_0 20 | 21 | ```python 22 | nums = [7, 5, 5, 12] 23 | non_adjacent_sum(nums) # -> 19 24 | ``` 25 | 26 | ## test_0 27 | 28 | ```python 29 | nums = [7, 5, 5, 12, 17, 29] 30 | non_adjacent_sum(nums) # -> 48 31 | ``` 32 | 33 | ## test_0 34 | 35 | ```python 36 | nums = [ 37 | 72, 62, 10, 6, 20, 19, 42, 38 | 46, 24, 78, 30, 41, 75, 38, 39 | 23, 28, 66, 55, 12, 17, 9, 40 | 12, 3, 1, 19, 30, 50, 20 41 | ] 42 | non_adjacent_sum(nums) # -> 488 43 | ``` 44 | 45 | ## test_0 46 | 47 | ```python 48 | nums = [ 49 | 72, 62, 10, 6, 20, 19, 42, 46, 24, 78, 50 | 30, 41, 75, 38, 23, 28, 66, 55, 12, 17, 51 | 83, 80, 56, 68, 6, 22, 56, 96, 77, 98, 52 | 61, 20, 0, 76, 53, 74, 8, 22, 92, 37, 53 | 30, 41, 75, 38, 23, 28, 66, 55, 12, 17, 54 | 72, 62, 10, 6, 20, 19, 42, 46, 24, 78, 55 | 42 56 | ] 57 | non_adjacent_sum(nums) # -> 1465 58 | ``` 59 | -------------------------------------------------------------------------------- /arrays_and_strings/circular_array_loop/README.md: -------------------------------------------------------------------------------- 1 | ## **Problem Statement** 2 | 3 | We are given a circular array of non-zero integers, `nums`, where each integer represents the number of steps to be taken either forward or backward from its current index. Positive values indicate forward movement, while negative values imply backward movement. When reaching either end of the array, the traversal wraps around to the opposite end. 4 | 5 | A circular array is a type of array where the last element is followed by the first element, forming a loop or circle. 6 | 7 | The input array may contain a cycle, which is a sequence of indexes characterized by the following: 8 | 9 | - The sequence starts and ends at the same index. 10 | - The length of the sequence is at least two. 11 | - The loop must be in a single direction, forward or backward. 12 | 13 | **Note**: A cycle in the array does not have to originate at the beginning. It may begin from any point in the array. 14 | 15 | Your task is to determine if `nums` has a cycle. Return TRUE if there is a cycle. Otherwise return FALSE. 16 | 17 | ### Constraints 18 | 19 | > 1 ≤ nums.length ≤ 103 20 | 21 | > −5000 ≤ nums[i] ≤ 5000 22 | 23 | > nums[i] != 0 24 | 25 | 26 | ### Example 1 27 | Input: nums = [3, 1, 2] 28 | Output: True 29 | 30 | 31 | ### Example 2 32 | Input: nums = [-2, -1, -3] 33 | Output: True 34 | 35 | ### Example 3 36 | Input: nums = [2, 1, -1, -2] 37 | Output: False 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /linked_lists/insert_node/README.md: -------------------------------------------------------------------------------- 1 | # insert node 2 | 3 | Write a function, `insert_node`, that takes in the head of a linked list, a value, and an index. The function should insert a new node with the value into the list at the specified index. Consider the head of the linked list as index 0. The function should return the head of the resulting linked list. 4 | 5 | Do this in-place. 6 | 7 | You may assume that the input list is non-empty and the index is not greater than the length of the input list. 8 | 9 | ## test_00: 10 | 11 | ```python 12 | a = Node("a") 13 | b = Node("b") 14 | c = Node("c") 15 | d = Node("d") 16 | 17 | a.next = b 18 | b.next = c 19 | c.next = d 20 | 21 | # a -> b -> c -> d 22 | 23 | insert_node(a, 'x', 2) 24 | # a -> b -> x -> c -> d 25 | ``` 26 | 27 | ## test_01: 28 | 29 | ```python 30 | a = Node("a") 31 | b = Node("b") 32 | c = Node("c") 33 | d = Node("d") 34 | 35 | a.next = b 36 | b.next = c 37 | c.next = d 38 | 39 | # a -> b -> c -> d 40 | 41 | insert_node(a, 'v', 3) 42 | # a -> b -> c -> v -> d 43 | ``` 44 | 45 | ## test_02: 46 | 47 | ```python 48 | a = Node("a") 49 | b = Node("b") 50 | c = Node("c") 51 | d = Node("d") 52 | 53 | a.next = b 54 | b.next = c 55 | c.next = d 56 | 57 | # a -> b -> c -> d 58 | 59 | insert_node(a, 'm', 4) 60 | # a -> b -> c -> d -> m 61 | ``` 62 | 63 | ## test_03: 64 | 65 | ```python 66 | a = Node("a") 67 | b = Node("b") 68 | 69 | a.next = b 70 | 71 | # a -> b 72 | 73 | insert_node(a, 'z', 0) 74 | # z -> a -> b 75 | ``` 76 | -------------------------------------------------------------------------------- /dynamic_programming/array_stepper/README.md: -------------------------------------------------------------------------------- 1 | # array stepper 2 | 3 | Write a function, `array_stepper`, that takes in a list of numbers as an argument. You start at the first position of the list. The function should return a boolean indicating whether or not it is possible to reach the last position of the list. When situated at some position of the list, you may take a maximum number of steps based on the number at that position. 4 | 5 | For example, given: 6 | 7 | idx = 0 1 2 3 4 5 8 | numbers = [2, 4, 2, 0, 0, 1] 9 | 10 | The answer is True. 11 | We start at idx 0, we could take 1 step or 2 steps forward. 12 | The correct choice is to take 1 step to idx 1. 13 | Then take 4 steps forward to the end at idx 5. 14 | ```python 15 | array_stepper([2, 4, 2, 0, 0, 1]) # -> True 16 | ``` 17 | 18 | ```python 19 | array_stepper([2, 3, 2, 0, 0, 1]) # -> False 20 | ``` 21 | 22 | ```python 23 | array_stepper([3, 1, 3, 1, 0, 1]) # -> True 24 | ``` 25 | 26 | ```python 27 | array_stepper([4, 1, 5, 1, 1, 1, 0, 4]) # -> True 28 | ``` 29 | 30 | ```python 31 | array_stepper([4, 1, 2, 1, 1, 1, 0, 4]) # -> False 32 | ``` 33 | 34 | ```python 35 | array_stepper([1, 1, 1, 1, 1, 0]) # -> True 36 | ``` 37 | 38 | ```python 39 | array_stepper([1, 1, 1, 1, 0, 0]) # -> False 40 | ``` 41 | 42 | ```python 43 | array_stepper([ 44 | 31, 30, 29, 28, 27, 45 | 26, 25, 24, 23, 22, 46 | 21, 20, 19, 18, 17, 47 | 16, 15, 14, 13, 12, 48 | 11, 10, 9, 8, 7, 6, 49 | 5, 3, 2, 1, 0, 0, 0 50 | ]) # -> False 51 | ``` 52 | -------------------------------------------------------------------------------- /graphs/knight_attack/README.md: -------------------------------------------------------------------------------- 1 | # knight attack 2 | 3 | A knight and a pawn are on a chess board. Can you figure out the minimum number of moves required for the knight to travel to the same position of the pawn? On a single move, the knight can move in an "L" shape; two spaces in any direction, then one space in a perpendicular direction. This means that on a single move, a knight has eight possible positions it can move to. 4 | 5 | Write a function, `knight_attack`, that takes in 5 arguments: 6 | 7 | n, kr, kc, pr, pc 8 | 9 | n = the length of the chess board 10 | kr = the starting row of the knight 11 | kc = the starting column of the knight 12 | pr = the row of the pawn 13 | pc = the column of the pawn 14 | 15 | The function should return a number representing the minimum number of moves required for the knight to land on top of the pawn. The knight cannot move out-of-bounds of the board. You can assume that rows and columns are 0-indexed. This means that if n = 8, there are 8 rows and 8 columns numbered 0 to 7. If it is not possible for the knight to attack the pawn, then return None. 16 | 17 | `knight_attack(8, 1, 1, 2, 2) # -> 2` 18 | 19 | `knight_attack(8, 1, 1, 2, 3) # -> 1` 20 | 21 | `knight_attack(8, 0, 3, 4, 2) # -> 3` 22 | 23 | `knight_attack(8, 0, 3, 5, 2) # -> 4` 24 | 25 | `knight_attack(24, 4, 7, 19, 20) # -> 10` 26 | 27 | `knight_attack(100, 21, 10, 0, 0) # -> 11` 28 | 29 | `knight_attack(3, 0, 0, 1, 2) # -> 1` 30 | 31 | `knight_attack(3, 0, 0, 1, 1) # -> None` 32 | -------------------------------------------------------------------------------- /binary_tree/how_high/README.md: -------------------------------------------------------------------------------- 1 | # how high 2 | 3 | Write a function, `how_high`, that takes in the root of a binary tree. The function should return a number representing the height of the tree. 4 | 5 | The height of a binary tree is defined as the maximal number of edges from the root node to any leaf node. 6 | 7 | If the tree is empty, return -1. 8 | 9 | ## test_00: 10 | 11 | ```python 12 | a = Node('a') 13 | b = Node('b') 14 | c = Node('c') 15 | d = Node('d') 16 | e = Node('e') 17 | f = Node('f') 18 | 19 | a.left = b 20 | a.right = c 21 | b.left = d 22 | b.right = e 23 | c.right = f 24 | 25 | # a 26 | # / \ 27 | # b c 28 | # / \ \ 29 | # d e f 30 | 31 | how_high(a) # -> 2 32 | ``` 33 | 34 | ## test_01: 35 | 36 | ```python 37 | a = Node('a') 38 | b = Node('b') 39 | c = Node('c') 40 | d = Node('d') 41 | e = Node('e') 42 | f = Node('f') 43 | g = Node('g') 44 | 45 | a.left = b 46 | a.right = c 47 | b.left = d 48 | b.right = e 49 | c.right = f 50 | e.left = g 51 | 52 | # a 53 | # / \ 54 | # b c 55 | # / \ \ 56 | # d e f 57 | # / 58 | # g 59 | 60 | how_high(a) # -> 3 61 | ``` 62 | 63 | ## test_02: 64 | 65 | ```python 66 | a = Node('a') 67 | c = Node('c') 68 | 69 | a.right = c 70 | 71 | # a 72 | # \ 73 | # c 74 | 75 | how_high(a) # -> 1 76 | ``` 77 | 78 | ## test_03: 79 | 80 | ```python 81 | a = Node('a') 82 | 83 | # a 84 | 85 | how_high(a) # -> 0 86 | ``` 87 | 88 | ## test_04: 89 | 90 | ```python 91 | how_high(None) # -> -1 92 | ``` 93 | -------------------------------------------------------------------------------- /linked_lists/longest_streak/README.md: -------------------------------------------------------------------------------- 1 | # longest streak 2 | 3 | Write a function, `longest_streak`, that takes in the head of a linked list as an argument. The function should return the length of the longest consecutive streak of the same value within the list. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node(5) 9 | b = Node(5) 10 | c = Node(7) 11 | d = Node(7) 12 | e = Node(7) 13 | f = Node(6) 14 | 15 | a.next = b 16 | b.next = c 17 | c.next = d 18 | d.next = e 19 | e.next = f 20 | 21 | # 5 -> 5 -> 7 -> 7 -> 7 -> 6 22 | 23 | longest_streak(a) # 3 24 | ``` 25 | 26 | ## test_01: 27 | 28 | ```python 29 | a = Node(3) 30 | b = Node(3) 31 | c = Node(3) 32 | d = Node(3) 33 | e = Node(9) 34 | f = Node(9) 35 | 36 | a.next = b 37 | b.next = c 38 | c.next = d 39 | d.next = e 40 | e.next = f 41 | 42 | # 3 -> 3 -> 3 -> 3 -> 9 -> 9 43 | 44 | longest_streak(a) # 4 45 | ``` 46 | 47 | ## test_02: 48 | 49 | ```python 50 | a = Node(9) 51 | b = Node(9) 52 | c = Node(1) 53 | d = Node(9) 54 | e = Node(9) 55 | f = Node(9) 56 | 57 | a.next = b 58 | b.next = c 59 | c.next = d 60 | d.next = e 61 | e.next = f 62 | 63 | # 9 -> 9 -> 1 -> 9 -> 9 -> 9 64 | 65 | longest_streak(a) # 3 66 | ``` 67 | 68 | ## test_03: 69 | 70 | ```python 71 | a = Node(5) 72 | b = Node(5) 73 | 74 | a.next = b 75 | 76 | # 5 -> 5 77 | 78 | longest_streak(a) # 2 79 | ``` 80 | 81 | ## test_04: 82 | 83 | ```python 84 | a = Node(4) 85 | 86 | # 4 87 | 88 | longest_streak(a) # 1 89 | ``` 90 | 91 | ## test_05: 92 | 93 | ```python 94 | longest_streak(None) # 0 95 | ``` 96 | -------------------------------------------------------------------------------- /binary_tree/tree_sum/tree_sum_v2.py: -------------------------------------------------------------------------------- 1 | def tree_sum(root): 2 | """ 3 | Calculates the sum of all node values in a binary tree recursively. 4 | 5 | Args: 6 | root (Node): The root node of the binary tree. 7 | 8 | Returns: 9 | int: The sum of all node values in the binary tree. 10 | 11 | """ 12 | if root is None: # If the root is None, return 0 13 | return 0 14 | 15 | left_sum = tree_sum(root.left) # Recursively calculate the sum of left subtree nodes 16 | right_sum = tree_sum(root.right) # Recursively calculate the sum of right subtree nodes 17 | 18 | return root.val + left_sum + right_sum # Return the sum of node values including current node 19 | 20 | """ 21 | Time Complexity Analysis: 22 | - Recursive calls: O(n), where n is the number of nodes in the binary tree. 23 | Overall: O(n) 24 | 25 | Space Complexity Analysis: 26 | - Recursive call stack: O(h), where h is the height of the binary tree (worst-case scenario). 27 | Overall: O(h) 28 | 29 | Further Notes: 30 | This algorithm calculates the sum of all node values in a binary tree using recursive traversal. 31 | It recursively calculates the sum of values for the left subtree and the right subtree, and then 32 | adds the value of the current node. The base case is when the root is None, in which case the 33 | function returns 0. The time complexity is O(n), where n is the number of nodes in the binary tree, 34 | and the space complexity is O(h), where h is the height of the binary tree in the worst-case scenario. 35 | """ 36 | -------------------------------------------------------------------------------- /stacks/decompress_braces/README.md: -------------------------------------------------------------------------------- 1 | 2 | # decompress braces 3 | 4 | Write a function, `decompress_braces`, that takes in a compressed string as an argument. The function should return the string 5 | decompressed. 6 | 7 | The compression format of the input string is `'n{sub_string}'`, where the `sub_string` within braces should be repeated `n` times. 8 | 9 | You may assume that every number n is guaranteed to be an integer between 1 through 9. 10 | 11 | You may assume that the input is valid and the decompressed string will only contain alphabetic characters. 12 | 13 | ## test_00 14 | 15 | ```python 16 | decompress_braces("2{q}3{tu}v") 17 | ``` 18 | 19 | ## test_01 20 | 21 | ```python 22 | decompress_braces("2{q}3{tu}v") 23 | ``` 24 | 25 | 26 | ## test_02 27 | 28 | ```python 29 | decompress_braces("ch3{ao}") 30 | # -> chaoaoao 31 | ``` 32 | 33 | ## test_03 34 | 35 | ```python 36 | decompress_braces("2{y3{o}}s") 37 | # -> yoooyooos 38 | ``` 39 | 40 | ## test_04 41 | 42 | ```python 43 | decompress_braces("z3{a2{xy}b}") 44 | # -> zaxyxybaxyxybaxyxyb 45 | ``` 46 | 47 | ## test_05 48 | 49 | ```python 50 | decompress_braces("2{3{r4{e}r}io}") 51 | # -> reeeerreeeerreeeerioreeeerreeeerreeeerio 52 | ``` 53 | 54 | ## test_06 55 | 56 | ```python 57 | decompress_braces("go3{spinn2{ing}s}") 58 | # -> gospinningingsspinningingsspinningings 59 | ``` 60 | 61 | ## test_07 62 | 63 | ```python 64 | decompress_braces("2{l2{if}azu}l") 65 | # -> lififazulififazul 66 | ``` 67 | 68 | ## test_08 69 | 70 | ```python 71 | decompress_braces("3{al4{ec}2{icia}}") 72 | # -> alececececiciaiciaalececececiciaiciaalececececiciaicia 73 | ``` 74 | -------------------------------------------------------------------------------- /binary_tree/leaf_list/leaf_list_v2.py: -------------------------------------------------------------------------------- 1 | def leaf_list(root): 2 | """ 3 | Generates a list of leaf nodes' values in a binary tree using a recursive DFS approach. 4 | 5 | Args: 6 | root: The root node of the binary tree. 7 | 8 | Returns: 9 | A list of values of leaf nodes in the binary tree, or an empty list if the tree is empty. 10 | """ 11 | 12 | leaves = [] 13 | _leaf_list(root, leaves) # Call the helper function to traverse and collect leaves 14 | return leaves 15 | 16 | 17 | def _leaf_list(root, leaves): 18 | """ 19 | Recursive helper function to traverse the tree and collect leaf node values. 20 | 21 | Args: 22 | root: The current node being processed. 23 | leaves: A list to store the collected leaf node values. 24 | """ 25 | 26 | if root is None: 27 | return # Base case: Do nothing if the node is None 28 | 29 | if root.left is None and root.right is None: 30 | leaves.append(root.val) # Append leaf node value if it has no children 31 | 32 | _leaf_list(root.left, leaves) # Recursively process the left child 33 | _leaf_list(root.right, leaves) # Recursively process the right child 34 | 35 | """ 36 | Time Complexity: O(n), where n is the number of nodes 37 | The algorithm visits each node in the tree once using a recursive DFS approach. 38 | In the worst case, all nodes are visited, leading to linear time complexity. 39 | 40 | Space Complexity: O(n) in the worst case 41 | The recursion can lead to a call stack size proportional to the tree height 42 | in the worst-case scenario (e.g., a skewed tree). 43 | 44 | """ 45 | -------------------------------------------------------------------------------- /binary_tree/tree_min_value/README.md: -------------------------------------------------------------------------------- 1 | # tree min value 2 | 3 | Write a function, `tree_min_value`, that takes in the root of a binary tree that contains number values. The function should return the minimum value within the tree. 4 | 5 | You may assume that the input tree is non-empty. 6 | 7 | ## test_00: 8 | 9 | ```python 10 | a = Node(3) 11 | b = Node(11) 12 | c = Node(4) 13 | d = Node(4) 14 | e = Node(-2) 15 | f = Node(1) 16 | 17 | a.left = b 18 | a.right = c 19 | b.left = d 20 | b.right = e 21 | c.right = f 22 | 23 | # 3 24 | # / \ 25 | # 11 4 26 | # / \ \ 27 | # 4 -2 1 28 | tree_min_value(a) # -> -2 29 | ``` 30 | 31 | ## test_01: 32 | 33 | ```python 34 | a = Node(5) 35 | b = Node(11) 36 | c = Node(3) 37 | d = Node(4) 38 | e = Node(14) 39 | f = Node(12) 40 | 41 | a.left = b 42 | a.right = c 43 | b.left = d 44 | b.right = e 45 | c.right = f 46 | 47 | # 5 48 | # / \ 49 | # 11 3 50 | # / \ \ 51 | # 4 14 12 52 | 53 | tree_min_value(a) # -> 3 54 | ``` 55 | 56 | ## test_02: 57 | 58 | ```python 59 | a = Node(-1) 60 | b = Node(-6) 61 | c = Node(-5) 62 | d = Node(-3) 63 | e = Node(-4) 64 | f = Node(-13) 65 | g = Node(-2) 66 | h = Node(-2) 67 | 68 | a.left = b 69 | a.right = c 70 | b.left = d 71 | b.right = e 72 | c.right = f 73 | e.left = g 74 | f.right = h 75 | 76 | # -1 77 | # / \ 78 | # -6 -5 79 | # / \ \ 80 | # -3 -4 -13 81 | # / \ 82 | # -2 -2 83 | 84 | tree_min_value(a) # -> -13 85 | ``` 86 | 87 | ## test_03: 88 | 89 | ```python 90 | a = Node(42) 91 | 92 | # 42 93 | 94 | tree_min_value(a) # -> 42 95 | ``` 96 | -------------------------------------------------------------------------------- /dynamic_programming/knightly_number/README.md: -------------------------------------------------------------------------------- 1 | # knightly number 2 | 3 | A knight is on a chess board. Can you figure out the total number of ways the knight can move to a target position in exactly m moves? On a single move, the knight can move in an "L" shape; two spaces in any direction, then one space in a perpendicular direction. This means that on a single move, a knight has eight possible positions it can move to. 4 | 5 | Write a function, knightlyNumber, that takes in 6 arguments: 6 | 7 | n, m, kr, kc, pr, pc 8 | 9 | n = the length of the chess board 10 | m = the number of moves that must be used 11 | kr = the starting row of the knight 12 | kc = the starting column of the knight 13 | pr = the target row 14 | pc = the target column 15 | 16 | The function should return the number of different ways the knight can move to the target in exactly m moves. The knight can revisit positions of the board if needed. The knight cannot move out-of-bounds of the board. You can assume that rows and columns are 0-indexed. This means that if n = 8, there are 8 rows and 8 columns numbered 0 to 7. 17 | 18 | ```python 19 | knightly_number(8, 2, 4, 4, 5, 5) # -> 2 20 | ``` 21 | 22 | ```python 23 | knightly_number(8, 2, 7, 1, 7, 1) # -> 3 24 | ``` 25 | 26 | ```python 27 | knightly_number(8, 2, 5, 4, 5, 4) # -> 8 28 | ``` 29 | 30 | ```python 31 | knightly_number(8, 3, 5, 2, 4, 4) # -> 21 32 | ``` 33 | 34 | ```python 35 | knightly_number(20, 6, 18, 7, 10, 15) # -> 60 36 | ``` 37 | 38 | ```python 39 | knightly_number(20, 12, 8, 3, 9, 14) # -> 98410127 40 | ``` 41 | 42 | ```python 43 | knightly_number(8, 2, 0, 0, 1, 1) # -> 0 44 | ``` 45 | -------------------------------------------------------------------------------- /binary_tree/leaf_list/leaf_list_v1.py: -------------------------------------------------------------------------------- 1 | def leaf_list(root): 2 | """ 3 | Generates a list of leaf nodes' values in a binary tree using a Depth-First Search (DFS) approach. 4 | 5 | Args: 6 | root: The root node of the binary tree. 7 | 8 | Returns: 9 | A list of values of leaf nodes in the binary tree, or an empty list if the tree is empty. 10 | """ 11 | 12 | if root is None: # Handle empty tree case 13 | return [] 14 | 15 | stack = [root] # Initialize stack with root node 16 | leaf_list = [] # List to store leaf node values 17 | 18 | while stack: 19 | current = stack.pop() # Pop the top node from the stack 20 | 21 | # Check if the current node is a leaf node (has no children) 22 | if current.right is None and current.left is None: 23 | leaf_list.append(current.val) # Append leaf node value to the list 24 | 25 | # Otherwise, add children to the stack for further exploration (right child first, then left child) 26 | else: 27 | if current.right is not None: 28 | stack.append(current.right) 29 | if current.left is not None: 30 | stack.append(current.left) 31 | 32 | return leaf_list # Return the list of leaf node values 33 | """ 34 | Time Complexity: O(n), where n is the number of nodes 35 | The algorithm visits each node in the tree once using DFS. In the worst case, all nodes are visited, 36 | leading to linear time complexity. 37 | 38 | Space Complexity: O(n) in the worst case 39 | The stack can hold up to a maximum of n/2 nodes in a skewed tree, resulting in linear space complexity. 40 | 41 | """ 42 | -------------------------------------------------------------------------------- /linked_lists/merge_lists/README.md: -------------------------------------------------------------------------------- 1 | # merge lists 2 | 3 | Write a function, `merge_lists`, that takes in the head of two sorted linked lists as arguments. The function should merge the two lists together into single sorted linked list. The function should return the head of the merged linked list. 4 | 5 | Do this in-place, by mutating the original Nodes. 6 | 7 | You may assume that both input lists are non-empty and contain increasing sorted numbers. 8 | 9 | ## test_00: 10 | 11 | ```python 12 | a = Node(5) 13 | b = Node(7) 14 | c = Node(10) 15 | d = Node(12) 16 | e = Node(20) 17 | f = Node(28) 18 | a.next = b 19 | b.next = c 20 | c.next = d 21 | d.next = e 22 | e.next = f 23 | # 5 -> 7 -> 10 -> 12 -> 20 -> 28 24 | 25 | q = Node(6) 26 | r = Node(8) 27 | s = Node(9) 28 | t = Node(25) 29 | q.next = r 30 | r.next = s 31 | s.next = t 32 | # 6 -> 8 -> 9 -> 25 33 | 34 | merge_lists(a, q) 35 | # 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 12 -> 20 -> 25 -> 28 36 | ``` 37 | 38 | ## test_01: 39 | 40 | ```python 41 | a = Node(5) 42 | b = Node(7) 43 | c = Node(10) 44 | d = Node(12) 45 | e = Node(20) 46 | f = Node(28) 47 | a.next = b 48 | b.next = c 49 | c.next = d 50 | d.next = e 51 | e.next = f 52 | # 5 -> 7 -> 10 -> 12 -> 20 -> 28 53 | 54 | q = Node(1) 55 | r = Node(8) 56 | s = Node(9) 57 | t = Node(10) 58 | q.next = r 59 | r.next = s 60 | s.next = t 61 | # 1 -> 8 -> 9 -> 10 62 | 63 | merge_lists(a, q) 64 | # 1 -> 5 -> 7 -> 8 -> 9 -> 10 -> 10 -> 12 -> 20 -> 28 65 | ``` 66 | 67 | ## test_02: 68 | 69 | ```python 70 | h = Node(30) 71 | # 30 72 | 73 | p = Node(15) 74 | q = Node(67) 75 | p.next = q 76 | # 15 -> 67 77 | 78 | merge_lists(h, p) 79 | # 15 -> 30 -> 67 80 | ``` 81 | -------------------------------------------------------------------------------- /binary_tree/tree_includes/tree_includes_v2.py: -------------------------------------------------------------------------------- 1 | def tree_includes(root, target): 2 | """ 3 | Checks if a binary tree contains a specific value using depth-first search recursively. 4 | 5 | Args: 6 | root (Node): The root node of the binary tree. 7 | target: The value to search for in the binary tree. 8 | 9 | Returns: 10 | bool: True if the target value is found in the binary tree, False otherwise. 11 | 12 | """ 13 | if root is None: # If the root is None, return False 14 | return False 15 | 16 | if root.val == target: # If the value of the current node matches the target value, return True 17 | return True 18 | 19 | # Recursively check if the target value is present in the left or right subtree 20 | return tree_includes(root.left, target) or tree_includes(root.right, target) 21 | 22 | """ 23 | Time Complexity Analysis: 24 | - Recursive calls: O(n), where n is the number of nodes in the binary tree. 25 | Overall: O(n) 26 | 27 | Space Complexity Analysis: 28 | - Recursive call stack: O(h), where h is the height of the binary tree (worst-case scenario). 29 | Overall: O(h) 30 | 31 | Further Notes: 32 | This algorithm checks if a binary tree contains a specific value using depth-first search recursively. 33 | It starts from the root node and recursively checks if the target value is present in the left or 34 | right subtree. If the target value is found in any subtree, the function returns True; otherwise, 35 | it returns False. The time complexity is O(n), where n is the number of nodes in the binary tree, 36 | and the space complexity is O(h), where h is the height of the binary tree in the worst-case scenario. 37 | """ 38 | -------------------------------------------------------------------------------- /linked_lists/sum_list/sum_list_v2.py: -------------------------------------------------------------------------------- 1 | def sum_list(head): 2 | """ 3 | Calculates the sum of all values in a linked list using recursion. 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | 8 | Returns: 9 | int: The sum of all values in the linked list. 10 | """ 11 | 12 | if head is None: # Base case: If the list is empty, return 0 (sum of no elements) 13 | return 0 14 | 15 | # Recursive case: Add the current node's value and the sum of the remaining list 16 | return head.val + sum_list(head.next) 17 | """ 18 | Time Complexity: O(n) 19 | The recursive function traverses the entire linked list once, 20 | making a call for each node. This results in a linear time complexity 21 | proportional to the number of nodes (n) in the worst-case scenario. 22 | 23 | Space Complexity: O(n) 24 | Due to recursion, function calls create a call stack. Each call adds a 25 | stack frame containing information about the call, local variables, 26 | and return addresses. The depth of the stack grows proportionally 27 | to the number of nodes in the linked list, leading to a space complexity 28 | of O(n) in the worst-case scenario. 29 | 30 | Notes on Approach and Reasoning: 31 | This code implements a recursive approach to sum the linked list values. 32 | It checks for the base case (empty list) and returns 0 in that case. 33 | Otherwise, it recursively calls itself with the `head.next` node. 34 | The return value from the recursive call (sum of remaining elements) 35 | is added to the current node's value and returned. 36 | While concise, recursion can be less efficient for large linked lists 37 | due to the overhead of function calls and the call stack usage. 38 | """ 39 | -------------------------------------------------------------------------------- /linked_lists/tests/test_merge_lists.py: -------------------------------------------------------------------------------- 1 | from merge_lists.merge_lists_v1 import merge_lists as merge_lists_v1 2 | from merge_lists.merge_lists_v2 import merge_lists as merge_lists_v2 3 | from traversal.traversal_v1 import Node 4 | 5 | test_case = [ 6 | 5, 7 | 1, 8 | 15 9 | ] 10 | 11 | def test_merge_lists_5_6(): 12 | a = Node(5) 13 | b = Node(7) 14 | c = Node(10) 15 | d = Node(12) 16 | e = Node(20) 17 | f = Node(28) 18 | a.next = b 19 | b.next = c 20 | c.next = d 21 | d.next = e 22 | e.next = f 23 | 24 | q = Node(6) 25 | r = Node(8) 26 | s = Node(9) 27 | t = Node(25) 28 | q.next = r 29 | r.next = s 30 | s.next = t 31 | 32 | merge_lists_head_v1 = merge_lists_v1(a, q) 33 | assert merge_lists_head_v1.val == test_case[0] 34 | assert merge_lists_head_v1.next.val == 6 35 | 36 | def test_merge_lists_5_1(): 37 | a = Node(5) 38 | b = Node(7) 39 | c = Node(10) 40 | d = Node(12) 41 | e = Node(20) 42 | f = Node(28) 43 | a.next = b 44 | b.next = c 45 | c.next = d 46 | d.next = e 47 | e.next = f 48 | 49 | 50 | q = Node(1) 51 | r = Node(8) 52 | s = Node(9) 53 | t = Node(10) 54 | q.next = r 55 | r.next = s 56 | s.next = t 57 | 58 | merge_lists_head_v1 = merge_lists_v1(a, q) 59 | assert merge_lists_head_v1.val == test_case[1] 60 | assert merge_lists_head_v1.next.val == 5 61 | 62 | def test_merge_lists_30_15(): 63 | h = Node(30) 64 | 65 | p = Node(15) 66 | q = Node(67) 67 | p.next = q 68 | 69 | merge_lists_head_v1 = merge_lists_v1(h, p) 70 | assert merge_lists_head_v1.val == test_case[2] 71 | assert merge_lists_head_v1.next.val == 30 -------------------------------------------------------------------------------- /binary_tree/tree_value_count/tree_value_count_v1.py: -------------------------------------------------------------------------------- 1 | def tree_value_count(root, target): 2 | """ 3 | Count the occurrences of a target value in the binary tree. 4 | 5 | Args: 6 | - root: The root node of the binary tree. 7 | - target: The value to count occurrences of. 8 | 9 | Returns: 10 | - int: The number of occurrences of the target value in the binary tree. 11 | 12 | """ 13 | 14 | # Base case: If the root is None, return 0 15 | if root is None: 16 | return 0 17 | 18 | # Check if the value of the current node matches the target value 19 | match = 1 if root.val == target else 0 20 | 21 | # Recursive case: Count occurrences of the target value in the left and right subtrees 22 | left_count = tree_value_count(root.left, target) 23 | right_count = tree_value_count(root.right, target) 24 | 25 | # Return the sum of the occurrences in the current node, left subtree, and right subtree 26 | return match + left_count + right_count 27 | 28 | """ 29 | Time Complexity Analysis: 30 | - Each node is visited once: O(n), where n is the number of nodes in the binary tree. 31 | 32 | Space Complexity Analysis: 33 | - Recursive function calls: O(h), where h is the height of the binary tree (worst-case scenario). 34 | Overall: O(h) 35 | 36 | Further Notes: 37 | Depth-first traversal (recursive) is used to traverse the binary tree. 38 | At each node, the algorithm checks if the value of the node matches the target value. 39 | If it matches, it increments the count by 1. Then, it recursively counts occurrences 40 | of the target value in the left and right subtrees. 41 | The total count is the sum of matches in the current node, left subtree, and right subtree. 42 | """ 43 | -------------------------------------------------------------------------------- /linked_lists/remove_node/README.md: -------------------------------------------------------------------------------- 1 | # remove node 2 | 3 | Write a function, `remove_node`, that takes in the head of a linked list and a target value as arguments. The function should delete the node containing the target value from the linked list and return the head of the resulting linked list. If the target appears multiple times in the linked list, only remove the first instance of the target in the list. 4 | 5 | Do this in-place. 6 | 7 | You may assume that the input list is non-empty. 8 | 9 | You may assume that the input list contains the target. 10 | ## test_00: 11 | 12 | ```python 13 | a = Node("a") 14 | b = Node("b") 15 | c = Node("c") 16 | d = Node("d") 17 | e = Node("e") 18 | f = Node("f") 19 | 20 | a.next = b 21 | b.next = c 22 | c.next = d 23 | d.next = e 24 | e.next = f 25 | 26 | # a -> b -> c -> d -> e -> f 27 | 28 | remove_node(a, "c") 29 | # a -> b -> d -> e -> f 30 | ``` 31 | 32 | ## test_01: 33 | 34 | ```python 35 | x = Node("x") 36 | y = Node("y") 37 | z = Node("z") 38 | 39 | x.next = y 40 | y.next = z 41 | 42 | # x -> y -> z 43 | 44 | remove_node(x, "z") 45 | # x -> y 46 | 47 | ## test_02: 48 | 49 | ```python 50 | q = Node("q") 51 | r = Node("r") 52 | s = Node("s") 53 | 54 | q.next = r 55 | r.next = s 56 | 57 | # q -> r -> s 58 | 59 | remove_node(q, "q") 60 | # r -> s 61 | ``` 62 | 63 | ## test_03: 64 | 65 | ```python 66 | node1 = Node("h") 67 | node2 = Node("i") 68 | node3 = Node("j") 69 | node4 = Node("i") 70 | 71 | node1.next = node2 72 | node2.next = node3 73 | node3.next = node4 74 | 75 | # h -> i -> j -> i 76 | 77 | remove_node(node1, "i") 78 | # h -> j -> i 79 | ``` 80 | 81 | ## test_04: 82 | 83 | ```python 84 | t = Node("t") 85 | 86 | # t 87 | 88 | remove_node(t, "t") 89 | # None 90 | ``` 91 | -------------------------------------------------------------------------------- /linked_lists/tests/test_get_node_values.py: -------------------------------------------------------------------------------- 1 | from get_node_value.get_node_value_v1 import get_node_value as get_node_value_v1 2 | from get_node_value.get_node_value_v2 import get_node_value as get_node_value_v2 3 | from traversal.traversal_v1 import Node 4 | 5 | test_case = [ 6 | 'c', 'd', None, 'banana', 'mango' 7 | ] 8 | 9 | def test_get_node_value_c(): 10 | a = Node("a") 11 | b = Node("b") 12 | c = Node("c") 13 | d = Node("d") 14 | 15 | a.next = b 16 | b.next = c 17 | c.next = d 18 | assert get_node_value_v1(a, 2) == test_case[0] 19 | assert get_node_value_v2(a, 2) == test_case[0] 20 | 21 | def test_get_node_value_d(): 22 | a = Node("a") 23 | b = Node("b") 24 | c = Node("c") 25 | d = Node("d") 26 | 27 | a.next = b 28 | b.next = c 29 | c.next = d 30 | assert get_node_value_v1(a, 3) == test_case[1] 31 | assert get_node_value_v2(a, 3) == test_case[1] 32 | 33 | def test_get_node_value_none(): 34 | a = Node("a") 35 | b = Node("b") 36 | c = Node("c") 37 | d = Node("d") 38 | 39 | a.next = b 40 | b.next = c 41 | c.next = d 42 | assert get_node_value_v1(a, 7) == test_case[2] 43 | assert get_node_value_v2(a, 7) == test_case[2] 44 | 45 | def test_get_node_value_banana(): 46 | node1 = Node("banana") 47 | node2 = Node("mango") 48 | 49 | node1.next = node2 50 | assert get_node_value_v1(node1, 0) == test_case[3] 51 | assert get_node_value_v2(node1, 0) == test_case[3] 52 | 53 | def test_get_node_value_mango(): 54 | node1 = Node("banana") 55 | node2 = Node("mango") 56 | 57 | node1.next = node2 58 | assert get_node_value_v1(node1, 1) == test_case[4] 59 | assert get_node_value_v2(node1, 1) == test_case[4] -------------------------------------------------------------------------------- /linked_lists/traversal/traversal_v1.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | """ 3 | Represents a node in a singly linked list. 4 | 5 | Attributes: 6 | val (int): The value stored in the node. 7 | next (Node): A reference to the next node in the linked list, or None if it's the last node. 8 | """ 9 | 10 | def __init__(self, val): 11 | """ 12 | Initializes a new Node instance. 13 | 14 | Args: 15 | val (int): The value to store in the node. 16 | """ 17 | self.val = val 18 | self.next = None 19 | 20 | def traverse(head): 21 | """ 22 | Traverses a singly linked list and returns a list of all node values. 23 | 24 | Args: 25 | head (Node): The head node of the linked list. 26 | 27 | Returns: 28 | list[int]: A list containing the values of all nodes in the linked list in the order they appear. 29 | """ 30 | 31 | nodes = [] # Create an empty list to store node values 32 | current = head # Start at the head node 33 | 34 | while current: # Loop as long as current is not None 35 | nodes.append(current.val) # Append current node's value to the list 36 | current = current.next # Move to the next node in the linked list 37 | 38 | return nodes 39 | 40 | """ 41 | Time Complexity: O(n) 42 | 43 | The `traverse` function iterates through the entire linked list once, where n is the number of nodes. The number of 44 | times the loop iterates is directly proportional to the number of nodes in the list. 45 | 46 | Space Complexity: O(n) 47 | 48 | The function creates a new list `nodes` to store the values. The size of this list grows in proportion to the number 49 | of nodes in the linked list. 50 | 51 | """ 52 | -------------------------------------------------------------------------------- /arrays_and_strings/intersection/intersection_v4.py: -------------------------------------------------------------------------------- 1 | def intersection(a: list[int], b: list[int]) -> list[int]: 2 | """ 3 | Finds the intersection of two sets of elements using sets for efficient lookups. 4 | 5 | Args: 6 | a (list[int]): The first list of elements. 7 | b (list[int]): The second list of elements. 8 | 9 | Returns: 10 | list[int]: A list containing the elements that are present in both lists. 11 | """ 12 | 13 | # Convert list a to a set for efficient membership testing (O(1)) 14 | return set(a).intersection(b) 15 | 16 | # Time Complexity: O(n + m) 17 | # - Converting list a to a set takes O(n) time in the worst case (all unique elements). 18 | # - Finding the intersection using the `intersection` method of sets has an average time complexity 19 | # of O(k), where k is the number of elements in the intersection. In the worst case (no intersection), 20 | # it's still proportional to the size of the smaller set (dominant factor). 21 | 22 | # Space Complexity: O(min(n, m)) 23 | # - The set operation creates a new set to store the intersection elements. 24 | # - In the worst case, it will store all elements from the shorter list (min(n, m)). 25 | 26 | # Approach and Reasoning: 27 | # This approach leverages sets for efficient element lookups. By converting list `a` to a set, 28 | # it enables constant time (O(1)) membership checks using the `intersection` method of sets. 29 | # This significantly improves the efficiency compared to iterating through list `a` for each 30 | # element in list `b`. 31 | 32 | # Note: 33 | # - This approach assumes you're dealing with hashable elements (e.g., integers, strings). 34 | # If you have non-hashable elements, you might need to consider alternative data structures or approaches. 35 | -------------------------------------------------------------------------------- /arrays_and_strings/most_frequent_char/most_frequent_char_v2.py: -------------------------------------------------------------------------------- 1 | def most_frequent_char(s): 2 | """ 3 | Finds the most frequent character in a string. 4 | 5 | Args: 6 | s (str): The input string. 7 | 8 | Returns: 9 | str: The most frequent character in the string, or None if the string is empty. 10 | """ 11 | 12 | # Use the built-in max function with a custom key function 13 | return max(s, key=s.count) 14 | 15 | # Time Complexity: O(n) 16 | # The `max` function with a custom key is generally considered linear time (O(n)) in Python. 17 | # In this case, `s.count` is called for each character in the string (potentially n times). 18 | 19 | # Space Complexity: O(1) 20 | # The code uses constant additional space for temporary variables. 21 | 22 | # Reasoning and Approach: 23 | # This code utilizes the built-in `max` function along with a custom key function for efficient character counting and finding the most frequent one. 24 | # - **`max(s, key=s.count)`:** The `max` function finds the maximum element in an iterable. Here, the iterable is the string `s`. 25 | # The `key` argument specifies a function that takes an element (character) from `s` and returns a value to be used for comparison. 26 | # In this case, the key function is `s.count`, which counts the occurrences of the character within the entire string `s`. 27 | # Therefore, `max` effectively iterates through the string and compares characters based on their frequencies (determined by `s.count`). 28 | # It returns the character with the highest count (the most frequent one). 29 | 30 | # Note: 31 | # This approach is concise and efficient, but it might not be suitable for very large strings 32 | # as `s.count` can be called multiple times depending on the character distribution. 33 | -------------------------------------------------------------------------------- /binary_tree/tree_sum/tree_sum_v3.py: -------------------------------------------------------------------------------- 1 | def tree_sum(root): 2 | """ 3 | Calculates the sum of all node values in a binary tree iteratively using a stack. 4 | 5 | Args: 6 | root (Node): The root node of the binary tree. 7 | 8 | Returns: 9 | int: The sum of all node values in the binary tree. 10 | 11 | """ 12 | stack = [root] # Initialize stack with the root node 13 | sum = 0 # Initialize the sum of node values 14 | 15 | while stack: # Continue traversal until the stack is empty 16 | current = stack.pop() # Pop the top node from the stack 17 | sum += current.val # Add the value of the popped node to the sum 18 | 19 | if current.right: # If the popped node has a right child, push it onto the stack 20 | stack.append(current.right) 21 | 22 | if current.left: # If the popped node has a left child, push it onto the stack 23 | stack.append(current.left) 24 | 25 | return sum # Return the sum of all node values 26 | 27 | """ 28 | Time Complexity Analysis: 29 | - Pushing and popping nodes onto/from stack: O(n), where n is the number of nodes in the binary tree. 30 | Overall: O(n) 31 | 32 | Space Complexity Analysis: 33 | - Storing nodes in the stack: O(n), where n is the number of nodes in the binary tree. 34 | Overall: O(n) 35 | 36 | Further Notes: 37 | This algorithm calculates the sum of all node values in a binary tree iteratively using a stack. 38 | It starts from the root node and pushes nodes onto the stack in a depth-first traversal manner. 39 | Nodes are popped from the stack, and their values are added to the sum. The right child is pushed 40 | before the left child to ensure that the left subtree is processed first. The time complexity is 41 | O(n), and the space complexity is O(n). 42 | """ 43 | -------------------------------------------------------------------------------- /linked_lists/linked_list_find/linked_list_find_v1.py: -------------------------------------------------------------------------------- 1 | def linked_list_find(head, target): 2 | """ 3 | Searches a linked list for a specific target value. 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | target (int): The value to search for in the linked list. 8 | 9 | Returns: 10 | bool: True if the target value is found, False otherwise. 11 | """ 12 | 13 | current = head # Initialize a pointer to the head node 14 | 15 | while current is not None: # Iterate through the linked list 16 | if current.val == target: # Check if current node's value matches the target 17 | return True # Target found, return True 18 | 19 | current = current.next # Move to the next node in the list 20 | 21 | return False # Reached the end without finding the target, return False 22 | 23 | # Time Complexity: O(n) 24 | # The `while` loop iterates through each node in the linked list in the worst case. 25 | # This results in a linear time complexity proportional to the number of nodes (n). 26 | 27 | # Space Complexity: O(1) 28 | # The function uses constant extra space for the `current` pointer, 29 | # independent of the linked list's size. This is considered constant space complexity. 30 | 31 | # Notes on Approach and Reasoning: 32 | # This code utilizes an iterative approach to search for the target value within the linked list. 33 | # It starts with a pointer (`current`) referencing the head node. 34 | # A `while` loop iterates through the list as long as `current` is not None. 35 | # Inside the loop, the current node's value is compared to the target value. 36 | # If a match is found, the function immediately returns True. 37 | # If the loop completes without finding a match, it returns False, indicating the target wasn't present. 38 | -------------------------------------------------------------------------------- /binary_tree/leaf_list/README.md: -------------------------------------------------------------------------------- 1 | # leaf list 2 | 3 | Write a function, `leaf_list`, that takes in the root of a binary tree and returns a list containing the values of all leaf nodes in left-to-right order. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node("a") 9 | b = Node("b") 10 | c = Node("c") 11 | d = Node("d") 12 | e = Node("e") 13 | f = Node("f") 14 | 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # a 22 | # / \ 23 | # b c 24 | # / \ \ 25 | # d e f 26 | 27 | leaf_list(a) # -> [ 'd', 'e', 'f' ] 28 | ``` 29 | 30 | ## test_01: 31 | 32 | ```python 33 | a = Node("a") 34 | b = Node("b") 35 | c = Node("c") 36 | d = Node("d") 37 | e = Node("e") 38 | f = Node("f") 39 | g = Node("g") 40 | h = Node("h") 41 | 42 | a.left = b 43 | a.right = c 44 | b.left = d 45 | b.right = e 46 | c.right = f 47 | e.left = g 48 | f.right = h 49 | 50 | # a 51 | # / \ 52 | # b c 53 | # / \ \ 54 | # d e f 55 | # / \ 56 | # g h 57 | 58 | leaf_list(a) # -> [ 'd', 'g', 'h' ] 59 | ``` 60 | 61 | ## test_02: 62 | 63 | ```python 64 | a = Node(5) 65 | b = Node(11) 66 | c = Node(54) 67 | d = Node(20) 68 | e = Node(15) 69 | f = Node(1) 70 | g = Node(3) 71 | 72 | a.left = b 73 | a.right = c 74 | b.left = d 75 | b.right = e 76 | e.left = f 77 | e.right = g 78 | 79 | # 5 80 | # / \ 81 | # 11 54 82 | # / \ 83 | # 20 15 84 | # / \ 85 | # 1 3 86 | 87 | leaf_list(a) # -> [ 20, 1, 3, 54 ] 88 | ``` 89 | 90 | ## test_03: 91 | 92 | ```python 93 | x = Node('x') 94 | 95 | # x 96 | 97 | leaf_list(x) # -> [ 'x' ] 98 | ``` 99 | 100 | ## test_04: 101 | 102 | ```python 103 | leaf_list(None) # -> [ ] 104 | ``` 105 | -------------------------------------------------------------------------------- /binary_tree/max_root_leaf_path_sum/README.md: -------------------------------------------------------------------------------- 1 | # max root to leaf path sum 2 | 3 | Write a function, `max_path_sum`, that takes in the root of a binary tree that contains number values. The function should return the maximum sum of any root to leaf path within the tree. 4 | 5 | You may assume that the input tree is non-empty. 6 | 7 | ## test_00: 8 | 9 | ```python 10 | a = Node(3) 11 | b = Node(11) 12 | c = Node(4) 13 | d = Node(4) 14 | e = Node(-2) 15 | f = Node(1) 16 | 17 | a.left = b 18 | a.right = c 19 | b.left = d 20 | b.right = e 21 | c.right = f 22 | 23 | # 3 24 | # / \ 25 | # 11 4 26 | # / \ \ 27 | # 4 -2 1 28 | 29 | max_path_sum(a) # -> 18 30 | ``` 31 | 32 | ## test_01: 33 | 34 | ```python 35 | a = Node(5) 36 | b = Node(11) 37 | c = Node(54) 38 | d = Node(20) 39 | e = Node(15) 40 | f = Node(1) 41 | g = Node(3) 42 | 43 | a.left = b 44 | a.right = c 45 | b.left = d 46 | b.right = e 47 | e.left = f 48 | e.right = g 49 | 50 | # 5 51 | # / \ 52 | # 11 54 53 | # / \ 54 | # 20 15 55 | # / \ 56 | # 1 3 57 | 58 | max_path_sum(a) # -> 59 59 | ``` 60 | 61 | ## test_02: 62 | 63 | ```python 64 | a = Node(-1) 65 | b = Node(-6) 66 | c = Node(-5) 67 | d = Node(-3) 68 | e = Node(0) 69 | f = Node(-13) 70 | g = Node(-1) 71 | h = Node(-2) 72 | 73 | a.left = b 74 | a.right = c 75 | b.left = d 76 | b.right = e 77 | c.right = f 78 | e.left = g 79 | f.right = h 80 | 81 | # -1 82 | # / \ 83 | # -6 -5 84 | # / \ \ 85 | # -3 0 -13 86 | # / \ 87 | # -1 -2 88 | 89 | max_path_sum(a) # -> -8 90 | ``` 91 | 92 | ## test_03: 93 | 94 | ```python 95 | a = Node(42) 96 | 97 | # 42 98 | 99 | max_path_sum(a) # -> 42 100 | ``` 101 | -------------------------------------------------------------------------------- /linked_lists/longest_streak/longest_streak_v3.py: -------------------------------------------------------------------------------- 1 | def longest_streak(head): 2 | """ 3 | Finds the length of the longest streak of equal values in a linked list. 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | 8 | Returns: 9 | int: The length of the longest streak of equal values. 10 | """ 11 | 12 | current = head # Initialize current node pointer to the head 13 | curr_val = head.val # Store the value of the head node for comparison 14 | max_streak = 0 # Initialize variable to track the longest streak seen so far 15 | curr_streak = 0 # Initialize variable to track the current streak 16 | 17 | while current: # Iterate through the linked list 18 | if current.val == curr_val: # Check if current node's value equals the comparison value 19 | curr_streak += 1 # Increment current streak if values are equal 20 | else: 21 | curr_val = current.val # Update comparison value if values differ 22 | curr_streak = 1 # Reset current streak for new potential streak 23 | 24 | if curr_streak > max_streak: # Check if current streak is longer than max streak 25 | max_streak = curr_streak # Update max streak if necessary 26 | 27 | current = current.next # Move current pointer to the next node 28 | 29 | return max_streak # Return the length of the longest streak 30 | 31 | # Time Complexity: O(n) 32 | # The while loop iterates through the linked list once in the worst case (n nodes). 33 | # This results in linear time complexity proportional to the list's length. 34 | 35 | # Space Complexity: O(1) 36 | # The function uses constant extra space for variables (max_streak, curr_streak, curr_val), 37 | # independent of the linked list's size. This is considered constant space complexity. 38 | -------------------------------------------------------------------------------- /binary_tree/tree_min_value/tree_min_value_v2.py: -------------------------------------------------------------------------------- 1 | def tree_min_value(root): 2 | """ 3 | Finds the minimum value in a binary tree using depth-first search recursively. 4 | 5 | Args: 6 | root (Node): The root node of the binary tree. 7 | 8 | Returns: 9 | int: The minimum value in the binary tree. 10 | 11 | """ 12 | if root is None: # Base case: If the root is None, return positive infinity 13 | return float("inf") 14 | 15 | # Recursively find the smallest value in the left subtree 16 | smallest_left_value = tree_min_value(root.left) 17 | # Recursively find the smallest value in the right subtree 18 | smallest_right_value = tree_min_value(root.right) 19 | 20 | # Return the minimum value among the current node value, smallest value in the left subtree, 21 | # and smallest value in the right subtree 22 | return min(root.val, smallest_left_value, smallest_right_value) 23 | 24 | """ 25 | Time Complexity Analysis: 26 | - Recursive calls: O(n), where n is the number of nodes in the binary tree. 27 | Overall: O(n) 28 | 29 | Space Complexity Analysis: 30 | - Recursive call stack: O(h), where h is the height of the binary tree (worst-case scenario). 31 | Overall: O(h) 32 | 33 | Further Notes: 34 | This algorithm finds the minimum value in a binary tree using depth-first search recursively. 35 | It starts from the root node and recursively finds the smallest value in the left subtree 36 | and the smallest value in the right subtree. Then, it returns the minimum value among the 37 | current node value, smallest value in the left subtree, and smallest value in the right subtree. 38 | The time complexity is O(n), where n is the number of nodes in the binary tree, and the space 39 | complexity is O(h), where h is the height of the binary tree in the worst-case scenario. 40 | """ 41 | -------------------------------------------------------------------------------- /graphs/has_path/has_path_v3.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, src, dst): 2 | """ 3 | Checks if there is a path between two nodes in a graph using Depth-First Search (DFS). 4 | 5 | Args: 6 | graph (dict): A dictionary representing the graph where keys are nodes and values are lists of neighboring nodes. 7 | src: The source node. 8 | dst: The destination node. 9 | 10 | Returns: 11 | bool: True if there is a path from src to dst, False otherwise. 12 | 13 | """ 14 | if src == dst: # If source and destination are the same, return True 15 | return True 16 | 17 | for neighbor in graph[src]: # Explore neighbors of the source node 18 | if has_path(graph, neighbor, dst) == True: # Recursively check for a path from neighbor to destination 19 | return True 20 | 21 | return False # Return False if no path is found 22 | 23 | """ 24 | Time Complexity Analysis: 25 | - Exploring each edge in the graph: O(e), where e is the number of edges. 26 | Overall: O(e) 27 | 28 | Space Complexity Analysis: 29 | - Recursive calls and call stack: O(n), where n is the number of nodes. 30 | Overall: O(n) 31 | 32 | Further Notes: 33 | This algorithm utilizes a recursive Depth-First Search (DFS) approach to check for a path between two nodes in a graph. 34 | It starts from the source node and explores its neighbors recursively. If the destination node is reached during 35 | exploration, the function returns True, indicating that a path exists. Otherwise, if no path is found from any of 36 | the neighbors to the destination, the function returns False, indicating that there is no path between the 37 | source and destination nodes. The time complexity is O(e), where e is the number of edges in the graph, and 38 | the space complexity is O(n), where n is the number of nodes in the graph. 39 | """ 40 | -------------------------------------------------------------------------------- /arrays_and_strings/pair_sum/pair_sum_v1.py: -------------------------------------------------------------------------------- 1 | def pair_sum(numbers, target_sum): 2 | """ 3 | Finds a pair of numbers in the given list that sum to the target sum. 4 | 5 | Args: 6 | numbers (list): A list of integers. 7 | target_sum (int): The target sum to find. 8 | 9 | Returns: 10 | tuple: A tuple containing the indices of the two numbers that sum to the target sum, 11 | or None if no such pair exists. 12 | """ 13 | 14 | # Create a dictionary to store seen numbers and their indices (Hash Table) 15 | previous_numbers = {} 16 | 17 | # Iterate through the numbers in the list 18 | for index, num in enumerate(numbers): 19 | # Calculate the complement (number needed to reach the target sum) 20 | complement = target_sum - num 21 | 22 | # Check if the complement has already been seen (exists in the dictionary) 23 | if complement in previous_numbers: 24 | # If found, return the indices of the pair (complement and current number) 25 | return (index, previous_numbers[complement]) 26 | 27 | # If not seen yet, add the current number and its index to the dictionary 28 | previous_numbers[num] = index 29 | 30 | # If no pair is found, return None 31 | return None 32 | 33 | # Time Complexity: O(n) 34 | # The code iterates through the list of numbers once to create the hash table (O(n)). 35 | # In the worst case, it iterates through the list again to find the complement (O(n)) for each number. 36 | # However, on average, it's likely to find a pair much earlier, resulting in an average case closer to O(n). 37 | 38 | # Space Complexity: O(n) 39 | # The `previous_numbers` dictionary stores numbers and their indices, potentially holding all unique numbers from the list. 40 | # In the worst case, this leads to a space complexity of O(n). 41 | -------------------------------------------------------------------------------- /binary_tree/depth_first_values/README.md: -------------------------------------------------------------------------------- 1 | # depth first values 2 | 3 | Write a function, `depth_first_values`, that takes in the root of a binary tree. The function should return a list containing all values of the tree in depth-first order. 4 | 5 | 6 | ## test_00: 7 | 8 | ```python 9 | a = Node('a') 10 | b = Node('b') 11 | c = Node('c') 12 | d = Node('d') 13 | e = Node('e') 14 | f = Node('f') 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # a 22 | # / \ 23 | # b c 24 | # / \ \ 25 | # d e f 26 | 27 | depth_first_values(a) 28 | # -> ['a', 'b', 'd', 'e', 'c', 'f'] 29 | ``` 30 | 31 | ## test_01: 32 | 33 | ```python 34 | a = Node('a') 35 | b = Node('b') 36 | c = Node('c') 37 | d = Node('d') 38 | e = Node('e') 39 | f = Node('f') 40 | g = Node('g') 41 | a.left = b 42 | a.right = c 43 | b.left = d 44 | b.right = e 45 | c.right = f 46 | e.left = g 47 | 48 | # a 49 | # / \ 50 | # b c 51 | # / \ \ 52 | # d e f 53 | # / 54 | # g 55 | 56 | depth_first_values(a) 57 | # -> ['a', 'b', 'd', 'e', 'g', 'c', 'f'] 58 | ``` 59 | 60 | ## test_02: 61 | 62 | ```python 63 | a = Node('a') 64 | # a 65 | depth_first_values(a) 66 | # -> ['a'] 67 | ``` 68 | 69 | ## test_03: 70 | 71 | ```python 72 | a = Node('a') 73 | b = Node('b') 74 | c = Node('c') 75 | d = Node('d') 76 | e = Node('e') 77 | a.right = b; 78 | b.left = c; 79 | c.right = d; 80 | d.right = e; 81 | 82 | # a 83 | # \ 84 | # b 85 | # / 86 | # c 87 | # \ 88 | # d 89 | # \ 90 | # e 91 | 92 | depth_first_values(a) 93 | # -> ['a', 'b', 'c', 'd', 'e'] 94 | ``` 95 | 96 | ## test_04: 97 | 98 | ```python 99 | depth_first_values(None) 100 | # -> [] 101 | ``` 102 | -------------------------------------------------------------------------------- /linked_lists/sum_list/sum_list_v1.py: -------------------------------------------------------------------------------- 1 | class Node: # Assuming this class definition exists elsewhere 2 | """ 3 | This class represents a node in a linked list. 4 | 5 | Attributes: 6 | val (int): The value stored in the node. 7 | next (Node): A reference to the next node in the linked list, or None if it's the last node. 8 | """ 9 | # ... Node class implementation (if not already provided) 10 | 11 | def sum_list(head): 12 | """ 13 | Calculates the sum of all values in a linked list. 14 | 15 | Args: 16 | head (Node): The head node of the linked list. 17 | 18 | Returns: 19 | int: The sum of all values in the linked list. 20 | """ 21 | 22 | sum = 0 # Initialize a variable to store the running sum 23 | 24 | while head is not None: # Iterate through the linked list as long as head is not None 25 | sum += head.val # Add the current node's value to the sum 26 | head = head.next # Move to the next node in the linked list 27 | 28 | return sum # Return the calculated sum 29 | 30 | """ 31 | Time Complexity: O(n) 32 | The while loop iterates through each node in the linked list once, 33 | resulting in a linear time complexity in the number of nodes (n). 34 | 35 | Space Complexity: O(1) 36 | The function uses constant extra space for the `sum` variable, 37 | independent of the linked list's size. This is considered constant space complexity. 38 | 39 | Notes on Approach and Reasoning: 40 | This code utilizes a simple iterative approach to traverse the linked list. 41 | It starts with an empty sum variable and iterates through each node using a `while` loop. 42 | Within the loop, the current node's value is added to the `sum` variable. 43 | Finally, after iterating through all nodes, the accumulated sum is returned. 44 | This approach avoids recursion and is generally more efficient for larger linked lists. 45 | """ 46 | -------------------------------------------------------------------------------- /binary_tree/tree_sum/tree_sum_v1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def tree_sum(root): 4 | """ 5 | Calculates the sum of all node values in a binary tree using breadth-first traversal. 6 | 7 | Args: 8 | root (Node): The root node of the binary tree. 9 | 10 | Returns: 11 | int: The sum of all node values in the binary tree. 12 | 13 | """ 14 | if not root: # If the tree is empty, return 0 15 | return 0 16 | 17 | sum = 0 # Initialize the sum of node values 18 | queue = deque([root]) # Initialize deque with the root node 19 | 20 | while queue: # Continue traversal until the deque is empty 21 | current = queue.popleft() # Dequeue the leftmost node from the deque 22 | sum += current.val # Add the value of the dequeued node to the sum 23 | 24 | if current.left: # If the dequeued node has a left child, enqueue it 25 | queue.append(current.left) 26 | 27 | if current.right: # If the dequeued node has a right child, enqueue it 28 | queue.append(current.right) 29 | 30 | return sum # Return the sum of all node values 31 | 32 | """ 33 | Time Complexity Analysis: 34 | - Enqueuing and dequeuing nodes: O(n), where n is the number of nodes in the binary tree. 35 | Overall: O(n) 36 | 37 | Space Complexity Analysis: 38 | - Storing nodes in the deque: O(n), where n is the number of nodes in the binary tree. 39 | Overall: O(n) 40 | 41 | Further Notes: 42 | This algorithm calculates the sum of all node values in a binary tree using breadth-first traversal. 43 | It starts from the root node and enqueues nodes at each level, and dequeues nodes in the order of 44 | their arrival, exploring nodes level by level. The values of the visited nodes are added to the sum, 45 | and the final sum is returned. The time complexity is O(n), and the space complexity is O(n). 46 | """ 47 | -------------------------------------------------------------------------------- /linked_lists/tests/test_linked_list_find.py: -------------------------------------------------------------------------------- 1 | from linked_list_find.linked_list_find_v1 import linked_list_find as linked_list_find_v1 2 | from linked_list_find.linked_list_find_v2 import linked_list_find as linked_list_find_v2 3 | from traversal.traversal_v1 import Node 4 | 5 | test_case = [True, True, False, True, True, False] 6 | 7 | def test_linked_list_find_c(): 8 | a = Node("a") 9 | b = Node("b") 10 | c = Node("c") 11 | d = Node("d") 12 | 13 | a.next = b 14 | b.next = c 15 | c.next = d 16 | 17 | assert linked_list_find_v1(a,"c") == test_case[0] 18 | assert linked_list_find_v2(a,"c")==test_case[0] 19 | 20 | 21 | def test_linked_list_find_d(): 22 | a = Node("a") 23 | b = Node("b") 24 | c = Node("c") 25 | d = Node("d") 26 | 27 | a.next = b 28 | b.next = c 29 | c.next = d 30 | 31 | assert linked_list_find_v1(a,"d")==test_case[1] 32 | assert linked_list_find_v2(a,"d")==test_case[1] 33 | 34 | 35 | def test_linked_list_find_q(): 36 | a = Node("a") 37 | b = Node("b") 38 | c = Node("c") 39 | d = Node("d") 40 | 41 | a.next = b 42 | b.next = c 43 | c.next = d 44 | 45 | assert linked_list_find_v1(a,"q")==test_case[2] 46 | assert linked_list_find_v2(a,"q")==test_case[2] 47 | 48 | 49 | def test_linked_list_find_jason(): 50 | node1 = Node("jason") 51 | node2 = Node("leneli") 52 | 53 | node1.next = node2 54 | 55 | assert linked_list_find_v1(node1,"jason")==test_case[3] 56 | assert linked_list_find_v2(node1,"jason")==test_case[3] 57 | 58 | 59 | def test_linked_list_find_42(): 60 | node1 = Node(42) 61 | assert linked_list_find_v1(node1,42)==test_case[4] 62 | assert linked_list_find_v2(node1,42)==test_case[4] 63 | 64 | 65 | def test_linked_list_find_100(): 66 | node1 = Node(42) 67 | assert linked_list_find_v1(node1,100)==test_case[5] 68 | assert linked_list_find_v2(node1,100)==test_case[5] 69 | -------------------------------------------------------------------------------- /binary_tree/tree_min_value/tree_min_value_v3.py: -------------------------------------------------------------------------------- 1 | def tree_min_value(root): 2 | """ 3 | Finds the minimum value in a binary tree using depth-first search iteratively. 4 | 5 | Args: 6 | root (Node): The root node of the binary tree. 7 | 8 | Returns: 9 | int: The minimum value in the binary tree. 10 | 11 | """ 12 | stack = [root] # Initialize stack with the root node 13 | min_val = float("inf") # Initialize min_val to positive infinity 14 | 15 | while stack: # Continue traversal until the stack is empty 16 | current = stack.pop() # Pop the top node from the stack 17 | 18 | if current.val < min_val: # Update min_val if current node value is smaller 19 | min_val = current.val 20 | 21 | if current.left: # If the popped node has a left child, push it onto the stack 22 | stack.append(current.left) 23 | 24 | if current.right: # If the popped node has a right child, push it onto the stack 25 | stack.append(current.right) 26 | 27 | return min_val # Return the minimum value found in the binary tree 28 | 29 | """ 30 | Time Complexity Analysis: 31 | - Pushing and popping nodes from the stack: O(n), where n is the number of nodes in the binary tree. 32 | Overall: O(n) 33 | 34 | Space Complexity Analysis: 35 | - Storing nodes in the stack: O(n), where n is the number of nodes in the binary tree. 36 | Overall: O(n) 37 | 38 | Further Notes: 39 | This algorithm finds the minimum value in a binary tree using depth-first search iteratively. 40 | It starts from the root node and iteratively explores nodes in a depth-first manner, updating 41 | the minimum value whenever a smaller value is encountered during traversal. The time complexity 42 | is O(n), where n is the number of nodes in the binary tree, and the space complexity is O(h), 43 | where h is the height of the binary tree in the worst-case scenario. 44 | """ 45 | -------------------------------------------------------------------------------- /graphs/semesters_required/README.md: -------------------------------------------------------------------------------- 1 | # semesters required 2 | 3 | Write a function, `semesters_required`, that takes in a number of courses (n) and a list of prerequisites as arguments. Courses have ids ranging from 0 through n - 1. A single prerequisite of (A, B) means that course A must be taken before course B. Return the minimum number of semesters required to complete all n courses. There is no limit on how many courses you can take in a single semester, as long the prerequisites of a course are satisfied before taking it. 4 | 5 | Note that given prerequisite (A, B), you cannot take course A and course B concurrently in the same semester. You must take A in some semester before B. 6 | 7 | You can assume that it is possible to eventually complete all courses. 8 | 9 | ## test_00 10 | 11 | ```python 12 | num_courses = 6 13 | prereqs = [ 14 | (1, 2), 15 | (2, 4), 16 | (3, 5), 17 | (0, 5), 18 | ] 19 | semesters_required(num_courses, prereqs) # -> 3 20 | ``` 21 | 22 | ## test_01 23 | 24 | ```python 25 | num_courses = 7 26 | prereqs = [ 27 | (4, 3), 28 | (3, 2), 29 | (2, 1), 30 | (1, 0), 31 | (5, 2), 32 | (5, 6), 33 | ] 34 | semesters_required(num_courses, prereqs) # -> 5 35 | ``` 36 | 37 | ## test_02 38 | 39 | ```python 40 | num_courses = 5 41 | prereqs = [ 42 | (1, 0), 43 | (3, 4), 44 | (1, 2), 45 | (3, 2), 46 | ] 47 | semesters_required(num_courses, prereqs) # -> 2 48 | ``` 49 | 50 | ## test_03 51 | 52 | ```python 53 | num_courses = 12 54 | prereqs = [] 55 | semesters_required(num_courses, prereqs) # -> 1 56 | ``` 57 | 58 | ## test_04 59 | 60 | ```python 61 | num_courses = 3 62 | prereqs = [ 63 | (0, 2), 64 | (0, 1), 65 | (1, 2), 66 | ] 67 | semesters_required(num_courses, prereqs) # -> 3 68 | ``` 69 | 70 | ## test_05 71 | 72 | ```python 73 | num_courses = 6 74 | prereqs = [ 75 | (3, 4), 76 | (3, 0), 77 | (3, 1), 78 | (3, 2), 79 | (3, 5), 80 | ] 81 | semesters_required(num_courses, prereqs) # -> 2 82 | ``` 83 | -------------------------------------------------------------------------------- /arrays_and_strings/intersection/intersection_v3.py: -------------------------------------------------------------------------------- 1 | def intersection(a: list[int], b: list[int]) -> list[int]: 2 | """ 3 | Finds the intersection of two sets of elements. 4 | 5 | Args: 6 | a (list[int]): The first list of elements. 7 | b (list[int]): The second list of elements. 8 | 9 | Returns: 10 | list[int]: A list containing the elements that are present in both lists. 11 | """ 12 | 13 | # Create an empty list to store the intersection elements 14 | intersection = [] 15 | 16 | # Iterate through elements in list b 17 | for element in b: 18 | # Check if the element is present in list a (potentially inefficient) 19 | if element in a: 20 | # If found, add it to the intersection list 21 | intersection.append(element) 22 | 23 | # Return the list containing the intersection elements 24 | return intersection 25 | 26 | # Time Complexity: O(n * m) 27 | # - In the worst case, the nested loop iterates through all elements in list b (length m) 28 | # and checks for membership in list a (length n) for each element. 29 | # - This can result in a total of n * m comparisons. 30 | 31 | # Space Complexity: O(min(n, m)) 32 | # - The `intersection` list stores the elements found in both lists. 33 | # - In the worst case, it will store all elements from the shorter list (min(n, m)). 34 | 35 | # Approach and Reasoning: 36 | # This approach iterates through each element in list `b` and checks if it exists in list `a` using a linear search. 37 | # However, this can be inefficient for larger lists due to the nested loop structure leading to O(n * m) time complexity. 38 | 39 | # Note: 40 | # - This is a basic implementation for finding intersections. It's generally less efficient 41 | # than using sets, which offer constant time lookups (O(1)) for membership checks. 42 | # - Consider using the alternative approach with sets for better performance, especially 43 | # when dealing with larger datasets. 44 | -------------------------------------------------------------------------------- /binary_tree/diameter_of_tree/diameter_of_tree.py: -------------------------------------------------------------------------------- 1 | def calculate_diameter(root): 2 | """ 3 | Calculate the diameter of a binary tree. The diameter is defined as the 4 | length of the longest path between any two nodes in the tree. This path 5 | may or may NOT pass through the root. 6 | 7 | Parameters: 8 | root (Node): The root node of the binary tree. 9 | 10 | Returns: 11 | int: The diameter of the tree. 12 | """ 13 | if root is None: 14 | return 0 15 | 16 | # Calculate the height of the left and right subtrees 17 | # The addition of 1 ensures that each node's height calculation includes 18 | # the edge connecting it to its tallest child, in this case, the root node 19 | left_height = 1 + calculate_height(root.left) 20 | right_height = 1 + calculate_height(root.right) 21 | 22 | # Calculate the diameter passing through the root 23 | through_root_diameter = left_height + right_height 24 | 25 | # Calculate the diameter of the left and right subtrees 26 | left_diameter = calculate_diameter(root.left) 27 | right_diameter = calculate_diameter(root.right) 28 | 29 | # The diameter is the maximum of the three possible diameters 30 | return max(left_diameter, right_diameter, through_root_diameter) 31 | 32 | def calculate_height(root): 33 | """ 34 | Calculate the height of a binary tree. The height is defined as the number 35 | of edges in the longest path from the node to a leaf. 36 | 37 | Parameters: 38 | root (Node): The root node of the binary tree. 39 | 40 | Returns: 41 | int: The height of the tree. 42 | """ 43 | if root is None: 44 | return -1 45 | 46 | # Calculate the height of the left and right subtrees 47 | left_height = calculate_height(root.left) 48 | right_height = calculate_height(root.right) 49 | 50 | # The height of the current node is 1 plus the maximum of the heights of its subtrees 51 | return 1 + max(left_height, right_height) -------------------------------------------------------------------------------- /binary_tree/tree_levels/README.md: -------------------------------------------------------------------------------- 1 | # tree levels 2 | 3 | Write a function, `tree_levels`, that takes in the root of a binary tree. The function should return a 2-Dimensional list where each sublist represents a level of the tree. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node('a') 9 | b = Node('b') 10 | c = Node('c') 11 | d = Node('d') 12 | e = Node('e') 13 | f = Node('f') 14 | 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # a 22 | # / \ 23 | # b c 24 | # / \ \ 25 | # d e f 26 | 27 | tree_levels(a) # -> 28 | # [ 29 | # ['a'], 30 | # ['b', 'c'], 31 | # ['d', 'e', 'f'] 32 | # ] 33 | ``` 34 | 35 | ## test_01: 36 | 37 | ```python 38 | 39 | a = Node('a') 40 | b = Node('b') 41 | c = Node('c') 42 | d = Node('d') 43 | e = Node('e') 44 | f = Node('f') 45 | g = Node('g') 46 | h = Node('h') 47 | i = Node('i') 48 | 49 | a.left = b 50 | a.right = c 51 | b.left = d 52 | b.right = e 53 | c.right = f 54 | e.left = g 55 | e.right = h 56 | f.left = i 57 | 58 | # a 59 | # / \ 60 | # b c 61 | # / \ \ 62 | # d e f 63 | # / \ / 64 | # g h i 65 | 66 | tree_levels(a) # -> 67 | # [ 68 | # ['a'], 69 | # ['b', 'c'], 70 | # ['d', 'e', 'f'], 71 | # ['g', 'h', 'i'] 72 | # ] 73 | ``` 74 | 75 | ## test_02: 76 | 77 | ```python 78 | q = Node('q') 79 | r = Node('r') 80 | s = Node('s') 81 | t = Node('t') 82 | u = Node('u') 83 | v = Node('v') 84 | 85 | q.left = r 86 | q.right = s 87 | r.right = t 88 | t.left = u 89 | u.right = v 90 | 91 | # q 92 | # / \ 93 | # r s 94 | # \ 95 | # t 96 | # / 97 | # u 98 | # / 99 | # v 100 | 101 | tree_levels(q) # -> 102 | # [ 103 | # ['q'], 104 | # ['r', 's'], 105 | # ['t'], 106 | # ['u'], 107 | # ['v'] 108 | # ] 109 | ``` 110 | ## test_03: 111 | 112 | ```python 113 | tree_levels(None) # -> [] 114 | ``` 115 | -------------------------------------------------------------------------------- /binary_tree/tree_min_value/tree_min_value_v1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def tree_min_value(root): 4 | """ 5 | Finds the minimum value in a binary tree using breadth-first search iteratively. 6 | 7 | Args: 8 | root (Node): The root node of the binary tree. 9 | 10 | Returns: 11 | int: The minimum value in the binary tree. 12 | 13 | """ 14 | min_val = float("inf") # Initialize minimum value to positive infinity 15 | queue = deque([root]) # Initialize deque with the root node 16 | 17 | while queue: # Continue traversal until the deque is empty 18 | current = queue.popleft() # Dequeue the leftmost node from the deque 19 | 20 | if current.val < min_val: # Update minimum value if current node value is smaller 21 | min_val = current.val 22 | 23 | if current.left: # If the dequeued node has a left child, enqueue it 24 | queue.append(current.left) 25 | 26 | if current.right: # If the dequeued node has a right child, enqueue it 27 | queue.append(current.right) 28 | 29 | return min_val # Return the minimum value found in the binary tree 30 | 31 | """ 32 | Time Complexity Analysis: 33 | - Enqueuing and dequeuing nodes: O(n), where n is the number of nodes in the binary tree. 34 | Overall: O(n) 35 | 36 | Space Complexity Analysis: 37 | - Storing nodes in the deque: O(n), where n is the number of nodes in the binary tree. 38 | Overall: O(n) 39 | 40 | Further Notes: 41 | This algorithm finds the minimum value in a binary tree using breadth-first search iteratively. 42 | It starts from the root node and enqueues nodes at each level, dequeuing nodes in the order 43 | of their arrival, exploring nodes level by level. The minimum value is updated whenever a 44 | smaller value is encountered during traversal. The time complexity is O(n), where n is the 45 | number of nodes in the binary tree, and the space complexity is O(n). 46 | """ 47 | -------------------------------------------------------------------------------- /graphs/has_path/has_path_v1.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, src, dst): 2 | """ 3 | Checks if there is a path between two nodes in a graph using Depth-First Search (DFS). 4 | 5 | Args: 6 | graph (dict): A dictionary representing the graph where keys are nodes and values are lists of neighboring nodes. 7 | src: The source node. 8 | dst: The destination node. 9 | 10 | Returns: 11 | bool: True if there is a path from src to dst, False otherwise. 12 | 13 | """ 14 | stack = [src] # Initialize stack with the source node 15 | 16 | while stack: 17 | current = stack.pop() # Pop a node from the stack 18 | 19 | if current == dst: # If the current node is the destination, return True 20 | return True 21 | 22 | for neighbor in graph[current]: # Explore neighbors of the current node 23 | stack.append(neighbor) # Add neighbors to the stack 24 | 25 | return False # Return False if no path is found 26 | 27 | """ 28 | Time Complexity Analysis: 29 | - Exploring each edge in the graph: O(e), where e is the number of edges. 30 | Overall: O(e) 31 | 32 | Space Complexity Analysis: 33 | - Storing nodes in the stack: O(n), where n is the number of nodes. 34 | Overall: O(n) 35 | 36 | Further Notes: 37 | This algorithm utilizes an iterative Depth-First Search (DFS) approach to check for a path between two nodes in a graph. 38 | It starts from the source node and explores neighbors iteratively using a stack data structure. If the destination node 39 | is encountered during exploration, the function returns True, indicating that a path exists. Otherwise, if the stack 40 | becomes empty without encountering the destination node, the function returns False, indicating that there is no path 41 | between the source and destination nodes. The time complexity is O(e), where e is the number of edges in the graph, 42 | and the space complexity is O(n), where n is the number of nodes in the graph. 43 | """ 44 | -------------------------------------------------------------------------------- /binary_tree/breadth_first_values/README.md: -------------------------------------------------------------------------------- 1 | # breadth first values 2 | 3 | Write a function, `breadth_first_values`, that takes in the root of a binary tree. The function should return a list containing all values of the tree in breadth-first order. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node('a') 9 | b = Node('b') 10 | c = Node('c') 11 | d = Node('d') 12 | e = Node('e') 13 | f = Node('f') 14 | 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # a 22 | # / \ 23 | # b c 24 | # / \ \ 25 | # d e f 26 | 27 | breadth_first_values(a) 28 | # -> ['a', 'b', 'c', 'd', 'e', 'f'] 29 | ``` 30 | 31 | ## test_01: 32 | 33 | ```python 34 | a = Node('a') 35 | b = Node('b') 36 | c = Node('c') 37 | d = Node('d') 38 | e = Node('e') 39 | f = Node('f') 40 | g = Node('g') 41 | h = Node('h') 42 | 43 | a.left = b 44 | a.right = c 45 | b.left = d 46 | b.right = e 47 | c.right = f 48 | e.left = g 49 | f.right = h 50 | 51 | # a 52 | # / \ 53 | # b c 54 | # / \ \ 55 | # d e f 56 | # / \ 57 | # g h 58 | 59 | breadth_first_values(a) 60 | # -> ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 61 | ``` 62 | 63 | ## test_02: 64 | 65 | ```python 66 | a = Node('a') 67 | 68 | # a 69 | 70 | breadth_first_values(a) 71 | # -> ['a'] 72 | ``` 73 | 74 | ## test_03: 75 | 76 | ```python 77 | a = Node('a') 78 | b = Node('b') 79 | c = Node('c') 80 | d = Node('d') 81 | e = Node('e') 82 | x = Node('x') 83 | 84 | a.right = b 85 | b.left = c 86 | c.left = x 87 | c.right = d 88 | d.right = e 89 | 90 | # a 91 | # \ 92 | # b 93 | # / 94 | # c 95 | # / \ 96 | # x d 97 | # \ 98 | # e 99 | 100 | breadth_first_values(a) 101 | # -> ['a', 'b', 'c', 'x', 'd', 'e'] 102 | ``` 103 | 104 | ## test_04: 105 | 106 | ```python 107 | breadth_first_values(None) 108 | # -> [] 109 | ``` 110 | -------------------------------------------------------------------------------- /binary_tree/level_averages/level_averages_v1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | from statistics import mean 3 | 4 | 5 | def level_averages(root): 6 | """ 7 | Calculates the average value of nodes at each level in a binary tree. 8 | 9 | Args: 10 | root: The root node of the binary tree. 11 | 12 | Returns: 13 | A list of floats, where each element represents the average value of nodes at a specific level. 14 | An empty list is returned if the tree is empty. 15 | """ 16 | 17 | if root is None: 18 | return [] # Handle empty tree case 19 | 20 | queue = deque([(root, 0)]) # Initialize queue with root and level 0 21 | level_averages = [] # List to store nodes at each level 22 | 23 | while queue: 24 | current, level = queue.popleft() # Dequeue the next node and its level 25 | 26 | # Check if a new level needs to be created in level_averages 27 | if len(level_averages) == level: 28 | level_averages.append([current.val]) # Create a new list for the level 29 | else: 30 | level_averages[level].append(current.val) # Append node value to existing level list 31 | 32 | # Add child nodes to the queue for processing 33 | if current.left: 34 | queue.append((current.left, level + 1)) 35 | if current.right: 36 | queue.append((current.right, level + 1)) 37 | 38 | # Calculate the average value for each level 39 | avgs = [mean(level) for level in level_averages] 40 | 41 | return avgs 42 | 43 | """ 44 | Time Complexity: O(n) where n is the number of nodes in the tree. 45 | The algorithm visits each node in the tree once using a Breadth-First Search (BFS) approach. 46 | 47 | Space Complexity: O(n) in the worst case. 48 | Reason: The queue can hold all nodes in the tree in the worst-case scenario of a complete binary tree. 49 | 50 | Further Notes: 51 | - The `mean` function from the `statistics` module is used to calculate the average efficiently. 52 | """ 53 | -------------------------------------------------------------------------------- /graphs/prereqs_available/README.md: -------------------------------------------------------------------------------- 1 | # prereqs possible 2 | 3 | Write a function, `prereqs_possible`, that takes in a number of courses (n) and prerequisites as arguments. Courses have ids ranging from 0 through n - 1. A single prerequisite of (A, B) means that course A must be taken before course B. The function should return a boolean indicating whether or not it is possible to complete all courses. 4 | 5 | ## test_00 6 | 7 | ```python 8 | numCourses = 6 9 | prereqs = [ 10 | (0, 1), 11 | (2, 3), 12 | (0, 2), 13 | (1, 3), 14 | (4, 5), 15 | ] 16 | prereqs_possible(numCourses, prereqs) # -> True 17 | ``` 18 | ## test_01 19 | 20 | ```python 21 | 22 | numCourses = 6 23 | prereqs = [ 24 | (0, 1), 25 | (2, 3), 26 | (0, 2), 27 | (1, 3), 28 | (4, 5), 29 | (3, 0), 30 | ] 31 | prereqs_possible(numCourses, prereqs) # -> False 32 | ``` 33 | ## test_02 34 | 35 | ```python 36 | 37 | numCourses = 5 38 | prereqs = [ 39 | (2, 4), 40 | (1, 0), 41 | (0, 2), 42 | (0, 4), 43 | ] 44 | prereqs_possible(numCourses, prereqs) # -> True 45 | ``` 46 | ## test_03 47 | 48 | ```python 49 | 50 | numCourses = 6 51 | prereqs = [ 52 | (2, 4), 53 | (1, 0), 54 | (0, 2), 55 | (0, 4), 56 | (5, 3), 57 | (3, 5), 58 | ] 59 | prereqs_possible(numCourses, prereqs) # -> False 60 | ``` 61 | ## test_04 62 | 63 | ```python 64 | 65 | numCourses = 8 66 | prereqs = [ 67 | (1, 0), 68 | (0, 6), 69 | (2, 0), 70 | (0, 5), 71 | (3, 7), 72 | (4, 3), 73 | ] 74 | prereqs_possible(numCourses, prereqs) # -> True 75 | ``` 76 | 77 | ## test_05 78 | 79 | ```python 80 | 81 | numCourses = 8 82 | prereqs = [ 83 | (1, 0), 84 | (0, 6), 85 | (2, 0), 86 | (0, 5), 87 | (3, 7), 88 | (7, 4), 89 | (4, 3), 90 | ] 91 | prereqs_possible(numCourses, prereqs) # -> False 92 | ``` 93 | 94 | ## test_06 95 | 96 | ```python 97 | 98 | numCourses = 42 99 | prereqs = [(6, 36)] 100 | prereqs_possible(numCourses, prereqs) # -> True# 101 | ``` 102 | -------------------------------------------------------------------------------- /binary_tree/level_averages/level_averages_v2.py: -------------------------------------------------------------------------------- 1 | from statistics import mean 2 | 3 | 4 | def level_averages(root): 5 | """ 6 | Calculates the average value of nodes at each level in a binary tree using a Depth-First Search (DFS) approach. 7 | 8 | Args: 9 | root: The root node of the binary tree. 10 | 11 | Returns: 12 | A list of floats, where each element represents the average value of nodes at a specific level. 13 | An empty list is returned if the tree is empty. 14 | """ 15 | 16 | levels = [] 17 | fill_levels(root, levels, 0) # Start filling levels from root at level 0 18 | 19 | avgs = [] 20 | for level in levels: 21 | avgs.append(mean(level)) # Calculate average for each level 22 | return avgs 23 | 24 | 25 | def fill_levels(root, levels, level_num): 26 | """ 27 | Recursive helper function to fill a list with node values at each level in the tree. 28 | 29 | Args: 30 | root: The current node being processed. 31 | levels: A list to store nodes at each level. 32 | level_num: The current level number. 33 | """ 34 | 35 | if root is None: 36 | return # Base case: Do nothing if node is None 37 | 38 | if level_num == len(levels): 39 | levels.append([root.val]) # Create a new list for the level if it doesn't exist 40 | else: 41 | levels[level_num].append(root.val) # Append node value to existing level list 42 | 43 | fill_levels(root.left, levels, level_num + 1) # Process left child at next level 44 | fill_levels(root.right, levels, level_num + 1) # Process right child at next level 45 | 46 | """ 47 | Time Complexity: O(n) 48 | The algorithm visits each node in the tree once using a DFS approach. In the worst case, 49 | all nodes are visited, leading to a linear time complexity. 50 | 51 | Space Complexity: O(n) in the worst case 52 | The recursion can lead to a call stack size proportional to the tree height 53 | in the worst case scenario (e.g., a skewed tree). 54 | 55 | """ 56 | -------------------------------------------------------------------------------- /binary_tree/tree_value_count/tree_value_count_v2.py: -------------------------------------------------------------------------------- 1 | def tree_value_count(root, target): 2 | """ 3 | Count the occurrences of a target value in the binary tree using iterative depth-first traversal. 4 | 5 | Args: 6 | - root: The root node of the binary tree. 7 | - target: The value to count occurrences of. 8 | 9 | Returns: 10 | - int: The number of occurrences of the target value in the binary tree. 11 | 12 | """ 13 | 14 | # Base case: If the root is None, return 0 15 | if root is None: 16 | return 0 17 | 18 | # Initialize a stack for iterative depth-first traversal 19 | stack = [root] 20 | count = 0 21 | 22 | # Iterate through the binary tree using iterative depth-first traversal 23 | while stack: 24 | current = stack.pop() 25 | 26 | # If the value of the current node matches the target value, increment count 27 | if current.val == target: 28 | count += 1 29 | 30 | # Add the right child to the stack if it exists 31 | if current.right is not None: 32 | stack.append(current.right) 33 | 34 | # Add the left child to the stack if it exists 35 | if current.left is not None: 36 | stack.append(current.left) 37 | 38 | return count 39 | 40 | """ 41 | Time Complexity Analysis: 42 | - Each node is visited once: O(n), where n is the number of nodes in the binary tree. 43 | 44 | Space Complexity Analysis: 45 | - Stack space for iterative traversal: O(h), where h is the height of the binary tree (worst-case scenario). 46 | Overall: O(h) 47 | 48 | Further Notes: 49 | Iterative depth-first traversal is used to traverse the binary tree. 50 | At each node, the algorithm checks if the value of the node matches the target value. 51 | If it matches, it increments the count by 1. 52 | The algorithm iterates through the tree while maintaining a stack of nodes to visit next. 53 | This approach avoids using recursion and instead uses a stack to keep track of nodes to visit. 54 | """ 55 | -------------------------------------------------------------------------------- /linked_lists/linked_list_find/linked_list_find_v2.py: -------------------------------------------------------------------------------- 1 | def linked_list_find(head, target): 2 | """ 3 | Searches a linked list for a specific target value using recursion. 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | target (int): The value to search for in the linked list. 8 | 9 | Returns: 10 | bool: True if the target value is found, False otherwise. 11 | """ 12 | 13 | if head is None: # Base case: Empty list, target not found 14 | return False 15 | 16 | if head.val == target: # Base case: Target found at the current node 17 | return True 18 | 19 | # Recursive case: Search for the target in the remaining list (head.next) 20 | return linked_list_find(head.next, target) 21 | 22 | # Time Complexity: O(n) 23 | # In the worst case, the recursive function calls itself for each node 24 | # in the linked list until the target is found or the end is reached. 25 | # This results in a linear time complexity proportional to the number of nodes (n). 26 | 27 | # Space Complexity: O(n) 28 | # Due to recursion, each function call creates a stack frame on the call stack. 29 | # This stack frame stores information about the function call, local variables, 30 | # and the return address. The depth of the call stack grows with the number of 31 | # recursive calls, which can be up to n (number of nodes) in the worst case. 32 | # This leads to a space complexity of O(n). 33 | 34 | # Notes on Approach and Reasoning: 35 | # This code implements a recursive approach to search for the target value. 36 | # It checks for the base cases: an empty list (not found) or a match at the current node (found). 37 | # Otherwise, it recursively calls itself with the `head.next` node, effectively searching the remaining list. 38 | # While concise, recursion can be less space-efficient for large linked lists 39 | # due to the overhead of function calls and the call stack usage. 40 | # For very large lists, an iterative approach might be preferable due to its constant space complexity. 41 | -------------------------------------------------------------------------------- /graphs/has_path/has_path_v2.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def has_path(graph, src, dst): 4 | """ 5 | Checks if there is a path between two nodes in a graph using Breadth-First Search (BFS). 6 | 7 | Args: 8 | graph (dict): A dictionary representing the graph where keys are nodes and values are lists of neighboring nodes. 9 | src: The source node. 10 | dst: The destination node. 11 | 12 | Returns: 13 | bool: True if there is a path from src to dst, False otherwise. 14 | 15 | """ 16 | queue = deque([src]) # Initialize queue with the source node 17 | 18 | while queue: 19 | current = queue.popleft() # Dequeue a node from the queue 20 | 21 | if current == dst: # If the current node is the destination, return True 22 | return True 23 | 24 | for neighbor in graph[current]: # Explore neighbors of the current node 25 | queue.append(neighbor) # Add neighbors to the queue 26 | 27 | return False # Return False if no path is found 28 | 29 | """ 30 | Time Complexity Analysis: 31 | - Exploring each edge in the graph: O(e), where e is the number of edges. 32 | Overall: O(e) 33 | 34 | Space Complexity Analysis: 35 | - Storing nodes in the queue: O(n), where n is the number of nodes. 36 | Overall: O(n) 37 | 38 | Further Notes: 39 | This algorithm utilizes an iterative Breadth-First Search (BFS) approach to check for a path between two nodes in a graph. 40 | It starts from the source node and explores neighbors iteratively using a queue data structure. If the destination node 41 | is encountered during exploration, the function returns True, indicating that a path exists. Otherwise, if the queue 42 | becomes empty without encountering the destination node, the function returns False, indicating that there is no path 43 | between the source and destination nodes. The time complexity is O(e), where e is the number of edges in the graph, 44 | and the space complexity is O(n), where n is the number of nodes in the graph. 45 | """ 46 | -------------------------------------------------------------------------------- /arrays_and_strings/intersection/intersection_v2.py: -------------------------------------------------------------------------------- 1 | def intersection(a: list[int], b: list[int]) -> list[int]: 2 | """ 3 | Finds the intersection of two sets of elements. 4 | 5 | Args: 6 | a (list[int]): The first list of elements. 7 | b (list[int]): The second list of elements. 8 | 9 | Returns: 10 | list[int]: A list containing the elements that are present in both lists. 11 | """ 12 | 13 | # Create a set from list a for efficient membership testing (O(1)) 14 | my_set = set(a) 15 | 16 | # Use list comprehension to filter elements from b that are in the set 17 | return [element for element in b if element in my_set] 18 | 19 | # Time Complexity: O(n + m) 20 | # - Converting list a to a set takes O(n) time in the worst case (all unique elements). 21 | # - Filtering elements from list b using list comprehension takes up to O(m) time 22 | # (depending on how many elements are in the intersection). 23 | # In total, the time complexity is linear in the sum of the lengths of the input lists (n + m). 24 | 25 | # Space Complexity: O(min(n, m)) 26 | # - The `intersection` list stores the elements found in both lists. 27 | # - In the worst case, it will store all elements from the shorter list (min(n, m)). 28 | 29 | # Approach and Reasoning: 30 | # This approach utilizes a set and list comprehension for efficient intersection. 31 | # 1. **Set Conversion:** Converting list `a` to a set (`my_set`) allows for fast membership checks 32 | # using the `in` operator, which has a time complexity of O(1) in the average case. 33 | # 2. **List Comprehension Filtration:** The list comprehension iterates through elements in `b` 34 | # and checks their membership in `my_set`. If an element is found in the set, it's included 35 | # in the resulting list. This approach is concise and avoids explicit loop construction. 36 | 37 | # Note: 38 | # - This approach assumes you're dealing with hashable elements (e.g., integers, strings). 39 | # If you have non-hashable elements, you might need to consider alternative data structures or approaches. 40 | -------------------------------------------------------------------------------- /binary_tree/tree_includes/tree_includes_v1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def tree_includes(root, target): 4 | """ 5 | Checks if a binary tree contains a specific value using breadth-first search. 6 | 7 | Args: 8 | root (Node): The root node of the binary tree. 9 | target: The value to search for in the binary tree. 10 | 11 | Returns: 12 | bool: True if the target value is found in the binary tree, False otherwise. 13 | 14 | """ 15 | if root is None: # If the root is None, return False 16 | return False 17 | 18 | queue = deque([root]) # Initialize deque with the root node 19 | 20 | while queue: # Continue traversal until the deque is empty 21 | current = queue.popleft() # Dequeue the leftmost node from the deque 22 | 23 | if current.val == target: # If the value of the dequeued node matches the target value, return True 24 | return True 25 | 26 | if current.left: # If the dequeued node has a left child, enqueue it 27 | queue.append(current.left) 28 | 29 | if current.right: # If the dequeued node has a right child, enqueue it 30 | queue.append(current.right) 31 | 32 | return False # If the target value is not found in the binary tree, return False 33 | 34 | """ 35 | Time Complexity Analysis: 36 | - Enqueuing and dequeuing nodes: O(n), where n is the number of nodes in the binary tree. 37 | Overall: O(n) 38 | 39 | Space Complexity Analysis: 40 | - Storing nodes in the deque: O(n), where n is the number of nodes in the binary tree. 41 | Overall: O(n) 42 | 43 | Further Notes: 44 | This algorithm checks if a binary tree contains a specific value using breadth-first search. 45 | It starts from the root node and enqueues nodes at each level, and dequeues nodes in the 46 | order of their arrival, exploring nodes level by level. If the target value is found in 47 | the binary tree, the function returns True; otherwise, it returns False. The time complexity 48 | is O(n), and the space complexity is O(n). 49 | """ 50 | -------------------------------------------------------------------------------- /arrays_and_strings/intersection/intersection_v1.py: -------------------------------------------------------------------------------- 1 | def intersection(a: list[int], b: list[int]) -> list[int]: 2 | """ 3 | Finds the intersection of two sets of elements. 4 | 5 | Args: 6 | a (list[int]): The first list of elements. 7 | b (list[int]): The second list of elements. 8 | 9 | Returns: 10 | list[int]: A list containing the elements that are present in both lists. 11 | """ 12 | 13 | # Create an empty list to store the intersection elements 14 | intersection = [] 15 | 16 | # Convert list a to a set for efficient membership testing (O(1)) 17 | my_set = {element for element in a} 18 | 19 | # Iterate through elements in list b 20 | for element in b: 21 | # Check if the element is present in the set (converted from list a) 22 | if element in my_set: 23 | # If found, add it to the intersection list 24 | intersection.append(element) 25 | 26 | # Return the list containing the intersection elements 27 | return intersection 28 | 29 | # Time Complexity: O(n + m) 30 | # - Converting list a to a set takes O(n) time in the worst case (all unique elements). 31 | # - Iterating through list b and checking for membership in the set takes O(m) time. 32 | # In total, the time complexity is linear in the sum of the lengths of the input lists (n + m). 33 | 34 | # Space Complexity: O(min(n, m)) 35 | # - The `intersection` list stores the elements found in both lists. 36 | # - In the worst case, it will store all elements from the shorter list (min(n, m)). 37 | 38 | # Approach and Reasoning: 39 | # This algorithm leverages sets for efficient element lookups. By converting list a to a set, it enables 40 | # constant time (O(1)) membership checks using the `in` operator. This significantly improves the efficiency 41 | # compared to iterating through list a for each element in list b. 42 | 43 | # Note: 44 | # - This approach assumes you're dealing with hashable elements (e.g., integers, strings). If you have non-hashable elements, 45 | # you might need to consider alternative data structures or approaches. 46 | -------------------------------------------------------------------------------- /arrays_and_strings/most_frequent_char/most_frequent_char_v3.py: -------------------------------------------------------------------------------- 1 | def most_frequent_char(s): 2 | """ 3 | Finds the most frequent character in a string. 4 | 5 | Args: 6 | s (str): The input string. 7 | 8 | Returns: 9 | str: The most frequent character in the string, or None if there's a tie. 10 | """ 11 | 12 | # Create a dictionary to track character frequencies 13 | tracker = {} 14 | for char in s: 15 | # Increment the count of the current character if it exists in the dictionary 16 | tracker[char] = tracker.get(char, 0) + 1 17 | 18 | # Find the key with the maximum value using max() and a custom key function 19 | most_frequent = max(tracker, key=tracker.get) 20 | 21 | return most_frequent 22 | 23 | # Time Complexity: O(n) 24 | # The code iterates through the string once to build the character frequency dictionary (O(n)). 25 | # The `max` function with a custom key is also considered linear time (O(n)) in Python. 26 | 27 | # Space Complexity: O(n) 28 | # The `tracker` dictionary stores character frequencies, potentially holding all unique characters from the string. 29 | # In the worst case, this leads to a space complexity of O(n). 30 | 31 | # Reasoning and Approach: 32 | # This code uses a dictionary and the `max` function with a custom key to efficiently find the most frequent character. 33 | # 1. **Character Frequency Tracking:** It iterates through the string and builds a dictionary (`tracker`) where each key is a character and the value is its frequency (number of occurrences). 34 | # 2. **Finding the Most Frequent Character (or First in Case of Tie):** It uses the `max` function to find the key (character) in the `tracker` dictionary with the maximum value (highest frequency). 35 | # The `key` argument in `max` specifies a custom function (`tracker.get`) that retrieves the corresponding value (frequency) for each key (character) during the comparison process. 36 | # 3. **Tie Handling (Implicit):** Since the code doesn't explicitly check for ties, the first character encountered with the maximum frequency will be returned by `max`. 37 | -------------------------------------------------------------------------------- /binary_tree/tree_value_count/README.md: -------------------------------------------------------------------------------- 1 | # tree value count 2 | 3 | Write a function, `tree_value_count`, that takes in the root of a binary tree and a target value. The function should return the number of times that the target occurs in the tree. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node(12) 9 | b = Node(6) 10 | c = Node(6) 11 | d = Node(4) 12 | e = Node(6) 13 | f = Node(12) 14 | 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # 12 22 | # / \ 23 | # 6 6 24 | # / \ \ 25 | # 4 6 12 26 | 27 | tree_value_count(a, 6) # -> 3 28 | ``` 29 | 30 | ## test_01: 31 | 32 | ```python 33 | a = Node(12) 34 | b = Node(6) 35 | c = Node(6) 36 | d = Node(4) 37 | e = Node(6) 38 | f = Node(12) 39 | a.right = c 40 | b.left = d 41 | b.right = e 42 | c.right = f 43 | 44 | # 12 45 | # / \ 46 | # 6 6 47 | # / \ \ 48 | # 4 6 12 49 | 50 | tree_value_count(a, 12) # -> 2 51 | ``` 52 | 53 | ## test_02: 54 | 55 | ```python 56 | a = Node(7) 57 | b = Node(5) 58 | c = Node(1) 59 | d = Node(1) 60 | e = Node(8) 61 | f = Node(7) 62 | g = Node(1) 63 | h = Node(1) 64 | 65 | a.left = b 66 | a.right = c 67 | b.left = d 68 | b.right = e 69 | c.right = f 70 | e.left = g 71 | f.right = h 72 | 73 | # 7 74 | # / \ 75 | # 5 1 76 | # / \ \ 77 | # 1 8 7 78 | # / \ 79 | # 1 1 80 | 81 | tree_value_count(a, 1) # -> 4 82 | ``` 83 | 84 | ## test_03: 85 | 86 | ```python 87 | a = Node(7) 88 | b = Node(5) 89 | c = Node(1) 90 | d = Node(1) 91 | e = Node(8) 92 | f = Node(7) 93 | g = Node(1) 94 | h = Node(1) 95 | 96 | a.left = b 97 | a.right = c 98 | b.left = d 99 | b.right = e 100 | c.right = f 101 | e.left = g 102 | f.right = h 103 | 104 | # 7 105 | # / \ 106 | # 5 1 107 | # / \ \ 108 | # 1 8 7 109 | # / \ 110 | # 1 1 111 | 112 | tree_value_count(a, 9) # -> 0 113 | ``` 114 | 115 | ## test_04: 116 | 117 | ```python 118 | tree_value_count(None, 42) # -> 0 119 | ``` 120 | -------------------------------------------------------------------------------- /arrays_and_strings/find_duplicate_number/find_duplicate_number.py: -------------------------------------------------------------------------------- 1 | def find_duplicate_number(nums: list[int]): 2 | # Initialize two pointers, both starting at the first element of the array. 3 | # These pointers will be used to detect a cycle in the array. 4 | fast = slow = nums[0] 5 | 6 | # Phase 1: Detect the cycle using Floyd's Tortoise and Hare algorithm. 7 | while True: 8 | # Move the slow pointer one step at a time. 9 | slow = nums[slow] 10 | # Move the fast pointer two steps at a time. 11 | fast = nums[nums[fast]] 12 | 13 | # If the slow and fast pointers meet, a cycle is detected. 14 | if slow == fast: 15 | break 16 | 17 | # Phase 2: Find the entry point of the cycle, which is the duplicate number. 18 | # Reset the slow pointer to the start of the array. 19 | slow = nums[0] 20 | 21 | # Move both pointers one step at a time until they meet. 22 | # The meeting point is the entry point of the cycle, which is the duplicate number. 23 | while slow != fast: 24 | slow = nums[slow] 25 | fast = nums[fast] 26 | 27 | # Return the duplicate number. 28 | return fast 29 | 30 | # Time Complexity: O(n) 31 | # The algorithm runs in linear time because both the cycle detection and the cycle entry point 32 | # finding phases require at most n steps each, where n is the number of elements in the array. 33 | 34 | # Space Complexity: O(1) 35 | # The algorithm uses a constant amount of extra space. It only uses a few integer variables 36 | # (slow, fast) and does not require any additional data structures that depend on the size of the input array. 37 | 38 | # Rationale: 39 | # The approach uses Floyd's Tortoise and Hare algorithm, which is typically used for cycle detection in linked lists. 40 | # Here, the array is treated as a linked list where each index points to the next index. 41 | # The duplicate number creates a cycle because it points back to an earlier index in the list. 42 | # This method efficiently finds the duplicate without modifying the input array or using extra space. -------------------------------------------------------------------------------- /binary_tree/tree_value_count/tree_value_count_v3.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def tree_value_count(root, target): 4 | """ 5 | Count the occurrences of a target value in the binary tree using iterative breadth-first traversal. 6 | 7 | Args: 8 | - root: The root node of the binary tree. 9 | - target: The value to count occurrences of. 10 | 11 | Returns: 12 | - int: The number of occurrences of the target value in the binary tree. 13 | 14 | """ 15 | 16 | # Base case: If the root is None, return 0 17 | if root is None: 18 | return 0 19 | 20 | # Initialize a queue for iterative breadth-first traversal 21 | queue = deque([root]) 22 | count = 0 23 | 24 | # Iterate through the binary tree using iterative breadth-first traversal 25 | while queue: 26 | current = queue.popleft() 27 | 28 | # If the value of the current node matches the target value, increment count 29 | if current.val == target: 30 | count += 1 31 | 32 | # Add the right child to the queue if it exists 33 | if current.right is not None: 34 | queue.append(current.right) 35 | 36 | # Add the left child to the queue if it exists 37 | if current.left is not None: 38 | queue.append(current.left) 39 | 40 | return count 41 | 42 | """ 43 | Time Complexity Analysis: 44 | - Each node is visited once: O(n), where n is the number of nodes in the binary tree. 45 | 46 | Space Complexity Analysis: 47 | - Queue space for iterative traversal: O(n), where n is the number of nodes in the binary tree to be stored in the queue 48 | (worst-case scenario). 49 | Overall: O(n) 50 | 51 | Further Notes: 52 | Iterative breadth-first traversal is used to traverse the binary tree level by level. 53 | At each level, the algorithm checks if the value of the node matches the target value. 54 | If it matches, it increments the count by 1. 55 | The algorithm iterates through the tree while maintaining a queue of nodes to visit next. 56 | This approach avoids using recursion and instead uses a queue to keep track of nodes to visit. 57 | """ 58 | -------------------------------------------------------------------------------- /linked_lists/is_univalue_list/is_univalue_list_v1.py: -------------------------------------------------------------------------------- 1 | def is_univalue_list(head): 2 | """ 3 | Checks if a linked list is a univalue list (all nodes have the same value). 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | 8 | Returns: 9 | bool: True if the list is a univalue list, False otherwise. 10 | """ 11 | 12 | if head is None: # Empty list is considered a univalue list with no value 13 | return True 14 | 15 | head_val = head.val # Store the value of the head node 16 | 17 | current = head.next # Initialize a pointer to the next node 18 | 19 | while current is not None: # Iterate through the list 20 | if current.val != head_val: # Check if current node's value differs from head 21 | return False 22 | current = current.next # Move to the next node 23 | 24 | # If the loop completes without finding a differing value, all nodes have the same value 25 | return True 26 | 27 | # Time Complexity: O(n) 28 | # The `while` loop iterates through each node in the linked list once in the worst case (n nodes). 29 | # This results in a linear time complexity proportional to the number of nodes (n). 30 | 31 | # Space Complexity: O(1) 32 | # The function uses constant extra space for pointers (`head`, `current`), and a variable (`head_val`), 33 | # independent of the linked list's size. This is considered constant space complexity. 34 | 35 | # Notes on Approach and Reasoning: 36 | # This code implements an iterative approach to check if a linked list is a univalue list. 37 | # It handles the empty list case as a univalue list. 38 | # It stores the value of the head node and iterates through the remaining nodes. 39 | # In each iteration, it compares the current node's value with the head node's value. 40 | # If a differing value is found, the list is not a univalue list, and the function returns False. 41 | # If the loop completes without finding a differing value, all nodes have the same value, 42 | # and the function returns True. 43 | # This iterative approach is efficient and avoids the overhead associated with recursion for this problem. 44 | -------------------------------------------------------------------------------- /linked_lists/remove_node/remove_node_v2.py: -------------------------------------------------------------------------------- 1 | def remove_node(head, target_val): 2 | """ 3 | Removes the first node from the linked list that has the specified target value (recursive approach). 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | target_val (int): The value to be removed from the list. 8 | 9 | Returns: 10 | Node: The head node of the modified linked list, potentially with the target node removed. 11 | """ 12 | 13 | if head is None: # Base case: Empty list, nothing to remove 14 | return None 15 | 16 | # Check if head node is the target node 17 | if head.val == target_val: 18 | return head.next # Return the next node as the new head 19 | 20 | # Recursively remove the target node from the rest of the list 21 | head.next = remove_node(head.next, target_val) 22 | 23 | # Return the (potentially modified) head node 24 | return head 25 | 26 | # Time Complexity: O(n) 27 | # In the worst case (target value not found), the function calls itself for each node 28 | # in the linked list, leading to a linear time complexity proportional to the list's length. 29 | 30 | # Space Complexity: O(n) 31 | # Due to recursion, the function call stack can grow up to the list's length in the worst case, 32 | # resulting in linear space complexity. 33 | 34 | # Notes on Approach and Reasoning: 35 | # This code implements a recursive approach to remove the first occurrence of a target value from a linked list. 36 | # It checks for an empty list as the base case. 37 | # If the head node itself is the target node, it returns the next node as the new head. 38 | # Otherwise, it recursively calls itself on the `head.next` node, passing the same target value. 39 | # The recursive call attempts to remove the target from the remaining list. 40 | # After the recursive call returns, it updates the `head.next` pointer with the modified list (potentially without the target). 41 | # Finally, it returns the (potentially modified) head node. 42 | 43 | # This recursive approach is concise but has a higher space complexity due to the call stack compared to the iterative approach. 44 | -------------------------------------------------------------------------------- /linked_lists/get_node_value/get_node_value_v2.py: -------------------------------------------------------------------------------- 1 | def get_node_value(head, index): 2 | """ 3 | Retrieves the value of a node at a specific index in a linked list using recursion. 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | index (int): The index of the node to retrieve the value from. 8 | 9 | Returns: 10 | int: The value stored in the node at the specified index, or None if the index is invalid. 11 | """ 12 | 13 | if head is None: # Base case: Empty list or index out of bounds (negative or exceeds length) 14 | return None 15 | 16 | if index == 0: # Base case: Target node is the head node 17 | return head.val 18 | 19 | # Recursive case: Decrement the index and traverse to the next node 20 | return get_node_value(head.next, index - 1) 21 | 22 | # Time Complexity: O(n) 23 | # In the worst case, the recursive function calls itself for each node 24 | # in the linked list until the target index (0) is reached or the end is reached. 25 | # This results in a linear time complexity proportional to the number of nodes (n). 26 | 27 | # Space Complexity: O(n) 28 | # Due to recursion, each function call creates a stack frame on the call stack. 29 | # This stack frame stores information about the function call, local variables, 30 | # and the return address. The depth of the call stack grows with the number of 31 | # recursive calls, which can be up to n (number of nodes) in the worst case. 32 | # This leads to a space complexity of O(n). 33 | 34 | # Notes on Approach and Reasoning: 35 | # This code implements a recursive approach to retrieve the value at a specific index. 36 | # It checks for base cases: an empty list or the target index being 0 (head node). 37 | # Otherwise, it recursively calls itself with `head.next` and decrements the index. 38 | # This effectively traverses the list until the target index is reached. 39 | # While concise, recursion can be less space-efficient for large linked lists 40 | # due to the overhead of function calls and the call stack usage. 41 | # For very large lists, an iterative approach might be preferable due to its constant space complexity. 42 | -------------------------------------------------------------------------------- /binary_tree/tree_levels/tree_levels_v1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | def tree_levels(root): 4 | """ 5 | Collects the values of a binary tree's nodes level-by-level using Breadth-First Search (BFS). 6 | 7 | Args: 8 | root: The root node of the binary tree. 9 | 10 | Returns: 11 | A list of lists, where each inner list represents the values of the nodes at a single level of the tree. 12 | If the tree is empty, returns an empty list. 13 | """ 14 | 15 | if root is None: # Handle empty tree case 16 | return [] 17 | 18 | queue = deque([root]) # Initialize queue with the root node 19 | level_nodes = [] # List to store nodes for each level 20 | 21 | while queue: # Continue until the queue is empty 22 | current_level_nodes = [] # List to store nodes at the current level 23 | 24 | for _ in range(len(queue)): # Process all nodes at the current level 25 | current = queue.popleft() # Get the first node from the queue 26 | current_level_nodes.append(current.val) # Append its value to the current level's list 27 | 28 | if current.left: # Add children to the queue for processing in subsequent levels 29 | queue.append(current.left) 30 | if current.right: 31 | queue.append(current.right) 32 | 33 | level_nodes.append(current_level_nodes) # Add the completed level to the result list 34 | 35 | return level_nodes # Return the list of levels 36 | 37 | """ 38 | **Time and Space Complexity:** 39 | 40 | - Time Complexity: O(n), where n is the number of nodes in the tree. Each node is visited once. 41 | - Space Complexity: O(n), due to the queue and level_nodes list, which can store up to n nodes in the worst case. 42 | 43 | **Approach and Reasoning:** 44 | 45 | - This algorithm employs a BFS approach, iteratively visiting nodes level by level. 46 | - It leverages a queue data structure to maintain the order of nodes to be processed. 47 | - It avoids recursion, making it suitable for large trees or those with potential stack overflow issues. 48 | - It explicitly tracks the current level to group nodes correctly. 49 | """ 50 | -------------------------------------------------------------------------------- /arrays_and_strings/anagrams/anagrams_v2.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | def anagrams(s1, s2): 4 | """ 5 | Checks if two strings are anagrams (have the same characters with the same frequency). 6 | 7 | Args: 8 | s1 (str): The first string. 9 | s2 (str): The second string. 10 | 11 | Returns: 12 | bool: True if the strings are anagrams, False otherwise. 13 | """ 14 | 15 | # Create frequency counters for both strings using the Counter class 16 | s1_dict = Counter(s1) # Count character occurrences in s1 17 | s2_dict = Counter(s2) # Count character occurrences in s2 18 | 19 | # Check if the character counts are equal (same characters with same frequencies) 20 | return s1_dict == s2_dict 21 | 22 | # Time Complexity: O(n + m) 23 | # The `Counter` constructor iterates through each character in the input string (n or m characters). 24 | # In the worst case, it needs to iterate through all characters. This results in a linear time complexity of O(n) for each string. 25 | # The `anagrams` function calls `Counter` twice, leading to a combined time complexity of O(n + m). 26 | 27 | # Space Complexity: O(n + m) 28 | # Both `anagrams` creates Counter objects (dictionaries) to store character frequencies. 29 | # In the worst case, each Counter object can hold all unique characters from the respective strings. 30 | # This leads to a space complexity of O(n) for each string and O(n + m) overall. 31 | 32 | # Reasoning and Approach: 33 | # This code utilizes the `Counter` class from the `collections` module for efficient anagram checking. 34 | # `Counter` provides a dictionary-like object that automatically counts the occurrences of elements in an iterable (like a string). 35 | # The code creates Counter objects for both strings (`s1_dict` and `s2_dict`). These objects efficiently store the frequency of each character. 36 | # Finally, it compares these counters using the `==` operator, which checks if they have the same keys (characters) with the same values (frequencies). 37 | # This approach leverages the built-in functionality of `Counter` for efficient character counting and comparison, resulting in a time complexity of O(n + m). 38 | -------------------------------------------------------------------------------- /linked_lists/linked_list_middle/linked_list_middle.py: -------------------------------------------------------------------------------- 1 | def get_middle_node(head): 2 | """ 3 | Finds the middle node of a singly linked list. 4 | 5 | This function uses the two-pointer technique, often referred to as the "Tortoise and Hare" approach, 6 | to efficiently find the middle node of a linked list. The slow pointer moves one step at a time, 7 | while the fast pointer moves two steps at a time. When the fast pointer reaches the end of the list, 8 | the slow pointer will be at the middle node. 9 | 10 | Args: 11 | head: The head node of the singly linked list. 12 | 13 | Returns: 14 | The middle node of the linked list. If the list is empty, returns None. 15 | """ 16 | slow = head # Initialize the slow pointer to the head of the list 17 | fast = head # Initialize the fast pointer to the head of the list 18 | 19 | # Traverse the list with the two pointers 20 | while fast and fast.next: 21 | slow = slow.next # Move the slow pointer one step 22 | fast = fast.next.next # Move the fast pointer two steps 23 | 24 | # When the loop ends, the slow pointer is at the middle node 25 | return slow 26 | 27 | # Time Complexity: O(n) 28 | # The algorithm runs in linear time because each pointer traverses the list at most once. 29 | # The fast pointer moves through the list twice as fast as the slow pointer, ensuring that 30 | # the entire list is traversed in O(n) time, where n is the number of nodes in the list. 31 | 32 | # Space Complexity: O(1) 33 | # The algorithm uses a constant amount of extra space. It only uses a few pointer variables 34 | # (slow, fast) and does not require any additional data structures that depend on the size of the input list. 35 | 36 | # Approach: 37 | # The function employs the "Tortoise and Hare" technique, which is an efficient way to find the middle 38 | # of a linked list. By moving one pointer at half the speed of the other, the slower pointer will 39 | # reach the middle of the list by the time the faster pointer reaches the end. This approach is 40 | # optimal for finding the middle node in a single pass without needing to know the length of the list 41 | # beforehand. -------------------------------------------------------------------------------- /linked_lists/is_univalue_list/is_univalue_list_v3.py: -------------------------------------------------------------------------------- 1 | def is_univalue_list(head, prev_val=None): 2 | """ 3 | Checks if a linked list is a univalue list (all nodes have the same value). 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | prev_val (int, optional): The value of the previous node (default: None). 8 | 9 | Returns: 10 | bool: True if the list is a univalue list, False otherwise. 11 | """ 12 | 13 | if head is None: # Empty list is considered a univalue list with no value 14 | return True 15 | 16 | # If prev_val is None, it's the first node. Set prev_val to the head's value. 17 | if prev_val is None: 18 | prev_val = head.val 19 | 20 | # Check if current node's value matches the previous value 21 | if head.val == prev_val: 22 | # Recursive call to check the remaining list with updated prev_val 23 | return is_univalue_list(head.next, prev_val) 24 | else: 25 | # Values differ, not a univalue list 26 | return False 27 | 28 | # Time Complexity: O(n) 29 | # The recursive function calls itself for each node in the linked list, leading to a linear time complexity. 30 | 31 | # Space Complexity: O(n) 32 | # Due to recursion, each function call creates a stack frame. 33 | # The depth of the stack frames grows with the number of nodes (n) in the worst case, resulting in O(n) space complexity. 34 | 35 | # Notes on Approach and Reasoning: 36 | # This code implements a recursive approach with an optional `prev_val` argument to track the previous node's value. 37 | # It handles empty lists as univalue lists. 38 | # It checks for the first node and sets `prev_val` to the head's value if necessary. 39 | # It compares the current node's value with `prev_val`. If they match, it makes a recursive call with updated `prev_val`. 40 | # If values differ, the list is not univalue, and it returns False. 41 | # This approach efficiently avoids redundant checks for the first node using `prev_val`. 42 | # It's concise but less space-efficient for large lists due to stack frames. 43 | # For very large lists, an iterative approach with a loop might be preferable for constant space complexity. 44 | -------------------------------------------------------------------------------- /binary_tree/level_averages/README.md: -------------------------------------------------------------------------------- 1 | # level averages 2 | 3 | Write a function, `level_averages`, that takes in the root of a binary tree that contains number values. The function should return a list containing the average value of each level. 4 | 5 | ## test_00: 6 | 7 | ```python 8 | a = Node(3) 9 | b = Node(11) 10 | c = Node(4) 11 | d = Node(4) 12 | e = Node(-2) 13 | f = Node(1) 14 | 15 | a.left = b 16 | a.right = c 17 | b.left = d 18 | b.right = e 19 | c.right = f 20 | 21 | # 3 22 | # / \ 23 | # 11 4 24 | # / \ \ 25 | # 4 -2 1 26 | 27 | level_averages(a) # -> [ 3, 7.5, 1 ] 28 | ``` 29 | 30 | ## test_01: 31 | 32 | ```python 33 | a = Node(5) 34 | b = Node(11) 35 | c = Node(54) 36 | d = Node(20) 37 | e = Node(15) 38 | f = Node(1) 39 | g = Node(3) 40 | 41 | a.left = b 42 | a.right = c 43 | b.left = d 44 | b.right = e 45 | e.left = f 46 | e.right = g 47 | 48 | # 5 49 | # / \ 50 | # 11 54 51 | # / \ 52 | # 20 15 53 | # / \ 54 | # 1 3 55 | 56 | level_averages(a) # -> [ 5, 32.5, 17.5, 2 ] 57 | ``` 58 | 59 | ## test_02: 60 | 61 | ```python 62 | a = Node(-1) 63 | b = Node(-6) 64 | c = Node(-5) 65 | d = Node(-3) 66 | e = Node(0) 67 | f = Node(45) 68 | g = Node(-1) 69 | h = Node(-2) 70 | 71 | a.left = b 72 | a.right = c 73 | b.left = d 74 | b.right = e 75 | c.right = f 76 | e.left = g 77 | f.right = h 78 | 79 | # -1 80 | # / \ 81 | # -6 -5 82 | # / \ \ 83 | # -3 0 45 84 | # / \ 85 | # -1 -2 86 | 87 | level_averages(a) # -> [ -1, -5.5, 14, -1.5 ] 88 | ``` 89 | 90 | ## test_03: 91 | 92 | ```python 93 | q = Node(13) 94 | r = Node(4) 95 | s = Node(2) 96 | t = Node(9) 97 | u = Node(2) 98 | v = Node(42) 99 | 100 | q.left = r 101 | q.right = s 102 | r.right = t 103 | t.left = u 104 | u.right = v 105 | 106 | # 13 107 | # / \ 108 | # 4 2 109 | # \ 110 | # 9 111 | # / 112 | # 2 113 | # / 114 | # 42 115 | 116 | level_averages(q) # -> [ 13, 3, 9, 2, 42 ] 117 | ``` 118 | 119 | ## test_04: 120 | 121 | ```python 122 | level_averages(None) # -> [ ] 123 | ``` 124 | -------------------------------------------------------------------------------- /graphs/connected_components/connected_components_count_v2.py: -------------------------------------------------------------------------------- 1 | def connected_components_count(graph): 2 | """ 3 | Counts the number of connected components in an undirected graph. 4 | 5 | Args: 6 | graph (dict): A dictionary representing the undirected graph where keys are nodes and values are lists of neighboring nodes. 7 | 8 | Returns: 9 | int: The number of connected components in the graph. 10 | 11 | """ 12 | visited = set() # Set to store visited nodes 13 | count = 0 # Counter for connected components 14 | 15 | def dfs(node): 16 | """ 17 | Depth-First Search (DFS) function to explore connected components starting from a given node. 18 | 19 | Args: 20 | node: The starting node for DFS exploration. 21 | 22 | """ 23 | visited.add(node) # Mark the current node as visited 24 | for neighbor in graph[node]: # Explore neighbors of the current node 25 | if neighbor not in visited: # If the neighbor is not visited, recursively explore it 26 | dfs(neighbor) 27 | 28 | for node in graph: # Iterate over each node in the graph 29 | if node not in visited: # If the node is not visited, explore it and its connected component 30 | dfs(node) 31 | count += 1 # Increment count for each new connected component 32 | 33 | return count # Return the count of connected components 34 | 35 | """ 36 | Time Complexity Analysis: 37 | - Exploring each edge in the graph: O(e), where e is the number of edges. 38 | Overall: O(e) 39 | 40 | Space Complexity Analysis: 41 | - Storing visited nodes: O(n), where n is the number of nodes. 42 | Overall: O(n) 43 | 44 | Further Notes: 45 | This algorithm utilizes a Depth-First Search (DFS) approach to find connected components in an undirected graph. 46 | It iterates over each node in the graph and explores each unvisited node and its neighbors recursively. 47 | If a new connected component is discovered during exploration, the count is incremented. The time complexity 48 | is O(e), where e is the number of edges in the graph, and the space complexity is O(n), where n is the 49 | number of nodes in the graph. 50 | """ 51 | -------------------------------------------------------------------------------- /linked_lists/get_node_value/get_node_value_v1.py: -------------------------------------------------------------------------------- 1 | def get_node_value(head, index): 2 | """ 3 | Retrieves the value of a node at a specific index in a linked list. 4 | 5 | Args: 6 | head (Node): The head node of the linked list. 7 | index (int): The index of the node to retrieve the value from. 8 | 9 | Returns: 10 | int: The value stored in the node at the specified index, or None if the index is invalid. 11 | """ 12 | 13 | if index < 0: # Handle negative indexes (invalid) 14 | return None 15 | 16 | counter = 0 17 | current = head 18 | 19 | while current is not None: # Iterate through the linked list 20 | if counter == index: # Check if current node's index matches the target index 21 | return current.val # Found the target node, return its value 22 | 23 | current = current.next # Move to the next node 24 | counter += 1 # Increment counter for the next iteration 25 | 26 | return None # Reached the end without finding the target index, return None 27 | 28 | # Time Complexity: O(n) 29 | # In the worst case, the `while` loop iterates through the entire linked list 30 | # (n nodes) if the target node is at the end or the index is out of bounds. 31 | # This results in a linear time complexity proportional to the number of nodes (n). 32 | 33 | # Space Complexity: O(1) 34 | # The function uses constant extra space for the `counter` and `current` variables, 35 | # independent of the linked list's size. This is considered constant space complexity. 36 | 37 | # Notes on Approach and Reasoning: 38 | # This code utilizes an iterative approach to access the value at a specific index within the linked list. 39 | # It first checks for an invalid negative index and returns None in that case. 40 | # Then, it iterates through the list using a `while` loop and a counter variable. 41 | # Inside the loop, the current node's index (counter) is compared to the target index. 42 | # If a match is found, the function immediately returns the current node's value. 43 | # If the loop completes without finding a matching index, it returns None, indicating an invalid index. 44 | # This iterative approach is generally efficient for accessing elements by index. 45 | --------------------------------------------------------------------------------