├── 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 |
--------------------------------------------------------------------------------