diff --git a/combination-sum/evan.py b/combination-sum/evan.py new file mode 100644 index 000000000..97ae7a983 --- /dev/null +++ b/combination-sum/evan.py @@ -0,0 +1,42 @@ +from typing import List + + +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + def backtrack(remain, combination, candidateIndex): + if remain == 0: + # if we reach the target, add the combination to the result + result.append(list(combination)) + return + elif remain < 0: + # if we exceed the target, no need to proceed + return + + for i in range(candidateIndex, len(candidates)): + # add the number to the current combination + combination.append(candidates[i]) + # continue the exploration with the current number + backtrack(remain - candidates[i], combination, i) + # backtrack by removing the number from the combination + combination.pop() + + result = [] + backtrack(target, [], 0) + + return result + + +# Time Complexity: O(N^(target / min(candidates))) +# The time complexity depends on: +# - N: The number of candidates (branching factor at each level). +# - target: The target sum we need to achieve. +# - min(candidates): The smallest element in candidates influences the maximum depth of the recursion tree. +# In the worst case, we branch N times at each level, and the depth can be target / min(candidates). +# Therefore, the overall time complexity is approximately O(N^(target / min(candidates))). + +# Space Complexity: O(target / min(candidates)) +# The space complexity is influenced by the maximum depth of the recursion tree: +# - target: The target sum we need to achieve. +# - min(candidates): The smallest element in candidates influences the maximum depth. +# The recursion stack can go as deep as target / min(candidates), so the space complexity is O(target / min(candidates)). +# Additionally, storing the results can take significant space, but it depends on the number of valid combinations found. diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/evan.py b/construct-binary-tree-from-preorder-and-inorder-traversal/evan.py new file mode 100644 index 000000000..eb4248a40 --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/evan.py @@ -0,0 +1,37 @@ +from typing import List, Optional + + +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + if not preorder or not inorder: + return None + + root_val = preorder.pop(0) + + root_inorder_index = inorder.index(root_val) + + left_tree_inorder = inorder[:root_inorder_index] + right_tree_inorder = inorder[root_inorder_index + 1 :] + + return TreeNode( + root_val, + self.buildTree(preorder, left_tree_inorder), + self.buildTree(preorder, right_tree_inorder), + ) + + +# Overall time complexity: O(N^2) +# - Finding the root in the inorder list and splitting it takes O(N) time in each call. +# - There are N nodes, so this operation is repeated N times. + +# Overall space complexity: O(N) +# - The primary space usage is the recursion stack which goes as deep as the height of the tree. +# - In the worst case (unbalanced tree), this can be O(N). +# - In the best case (balanced tree), this is O(log N). diff --git a/implement-trie-prefix-tree/evan.py b/implement-trie-prefix-tree/evan.py new file mode 100644 index 000000000..b04078e26 --- /dev/null +++ b/implement-trie-prefix-tree/evan.py @@ -0,0 +1,37 @@ +class Trie: + def __init__(self): + self.children = {} + self.is_end_of_word = False + + def insert(self, word: str) -> None: + node = self + + for char in word: + if char not in node.children: + node.children[char] = Trie() + + node = node.children[char] + + node.is_end_of_word = True + + def search(self, word: str) -> bool: + node = self + + for char in word: + if char not in node.children: + return False + + node = node.children[char] + + return node.is_end_of_word + + def startsWith(self, prefix: str) -> bool: + node = self + + for char in prefix: + if char not in node.children: + return False + + node = node.children[char] + + return True diff --git a/kth-smallest-element-in-a-bst/evan.py b/kth-smallest-element-in-a-bst/evan.py new file mode 100644 index 000000000..061a5e943 --- /dev/null +++ b/kth-smallest-element-in-a-bst/evan.py @@ -0,0 +1,40 @@ +# Definition for a binary tree node. +from typing import Optional + + +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +class Solution: + def kthSmallest(self, root: Optional[TreeNode], k: int) -> int: + result = [] + + def inorderTraverse(node: Optional[TreeNode]): + if node is None or len(result) >= k: + return + + inorderTraverse(node.left) + + if len(result) < k: + result.append(node.val) + + inorderTraverse(node.right) + + inorderTraverse(root) + + return result[k - 1] + + +# Time Complexity: O(N) +# In the worst case, we need to visit all the nodes in the tree. +# Thus, the time complexity is O(N), where N is the number of nodes in the tree. + +# Space Complexity: O(N) +# The space complexity is determined by the recursion stack and the result list. +# 1. Recursion stack: In the worst case (unbalanced tree), the recursion stack can go up to N levels deep, so the space complexity is O(N). +# In the best case (balanced tree), the recursion stack depth is log(N), so the space complexity is O(log N). +# 2. Result list: The result list stores up to k elements, so the space complexity is O(k). diff --git a/word-search/evan.py b/word-search/evan.py new file mode 100644 index 000000000..6315ff189 --- /dev/null +++ b/word-search/evan.py @@ -0,0 +1,40 @@ +from typing import List + + +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + def dfs(row, col, k): + if k == len(word): + return True + + # out of range + if row < 0 or row >= len(board) or col < 0 or col >= len(board[0]): + return False + + # char not found + if board[row][col] != word[k]: + return False + + temp = board[row][col] + + # mark visited char + board[row][col] = None + + result = ( + dfs(row + 1, col, k + 1) # top + or dfs(row - 1, col, k + 1) # bottom + or dfs(row, col + 1, k + 1) # right + or dfs(row, col - 1, k + 1) # left + ) + + # restore char + board[row][col] = temp + + return result + + for row in range(len(board)): + for col in range(len(board[0])): + if dfs(row, col, 0): + return True + + return False