diff --git a/binary-tree-level-order-traversal/bhyun-kim.py b/binary-tree-level-order-traversal/bhyun-kim.py new file mode 100644 index 000000000..0e63cb52c --- /dev/null +++ b/binary-tree-level-order-traversal/bhyun-kim.py @@ -0,0 +1,74 @@ +""" +102. Binary Tree Level Order Traversal +https://leetcode.com/problems/binary-tree-level-order-traversal/ +""" + +from typing import List, Optional + + +# Definition for a binary tree node. +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +""" +Solution + Breadth First Search (BFS) using Queue + + The problem is asking to return the node values at each level of the binary tree. + To solve this problem, we can use BFS algorithm with a queue. + We will traverse the tree level by level and store the node values at each level. + + 1. Initialize an empty list to store the output. + 2. Initialize an empty queue. + 3. Add the root node to the queue. + 4. While the queue is not empty, do the following: + - Get the size of the queue to know the number of nodes at the current level. + - Initialize an empty list to store the node values at the current level. + - Traverse the nodes at the current level and add the node values to the list. + - If the node has left or right child, add them to the queue. + - Decrease the level size by 1. + - Add the list of node values at the current level to the output. + 5. Return the output. + +Time complexity: O(N) + - We visit each node once + +Space complexity: O(N) + - The maximum number of nodes in the queue is the number of nodes at the last level + - The maximum number of nodes at the last level is N/2 + - The output list stores the node values at each level which is N + - Thus, the space complexity is O(N) + +""" + + +class Solution: + def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: + if root is None: + return + output = [] + queue = [] + queue.append(root) + + while len(queue) > 0: + level_size = len(queue) + level_output = [] + + while level_size > 0: + node = queue.pop(0) + level_output.append(node.val) + + if node.left is not None: + queue.append(node.left) + if node.right is not None: + queue.append(node.right) + + level_size -= 1 + + output.append(level_output) + + return output diff --git a/lowest-common-ancestor-of-a-binary-search-tree/bhyun-kim.py b/lowest-common-ancestor-of-a-binary-search-tree/bhyun-kim.py new file mode 100644 index 000000000..882a73db6 --- /dev/null +++ b/lowest-common-ancestor-of-a-binary-search-tree/bhyun-kim.py @@ -0,0 +1,38 @@ +""" +235. Lowest Common Ancestor of a Binary Search Tree +https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/ +""" + + +# Definition for a binary tree node. +class TreeNode: + def __init__(self, x): + self.val = x + self.left = None + self.right = None + + +""" +Solution: + - If both p and q are greater than the root, the lowest common ancestor is in the right subtree. + - If both p and q are less than the root, the lowest common ancestor is in the left subtree. + - Otherwise, the root is the lowest common ancestor. + +Time complexity: O(N) + - The function is called recursively for each node +Space complexity: O(N) + - Maximum depth of the recursion is the height of the tree +""" + + +class Solution: + def lowestCommonAncestor( + self, root: "TreeNode", p: "TreeNode", q: "TreeNode" + ) -> "TreeNode": + if p.val > root.val and q.val > root.val: + return self.lowestCommonAncestor(root.right, p, q) + + elif p.val < root.val and q.val < root.val: + return self.lowestCommonAncestor(root.left, p, q) + else: + return root diff --git a/remove-nth-node-from-end-of-list/bhyun-kim.py b/remove-nth-node-from-end-of-list/bhyun-kim.py new file mode 100644 index 000000000..5395888c3 --- /dev/null +++ b/remove-nth-node-from-end-of-list/bhyun-kim.py @@ -0,0 +1,82 @@ +""" +19. Remove Nth Node From End of List +https://leetcode.com/problems/remove-nth-node-from-end-of-list/ +""" + + +from typing import Optional + + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + +""" +Solution 1: +Two Pass Algorithm 1 + - First pass: Count the number of nodes and store the values in the list + - Second pass: Build the new list without the Nth node from the end + +Time complexity: O(N) + - Two passes are required +Space complexity: O(N) + - The list stores the values of the nodes + - The new nodes are created to build the new list +""" + + +class Solution1: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + vals = [] + + while head: + vals.append(head.val) + head = head.next + + dummy_node = ListNode() + tail = dummy_node + idx_to_remove = len(vals) - n + vals = vals[:idx_to_remove] + vals[idx_to_remove + 1 :] + + for v in vals: + tail.next = ListNode(val=v) + tail = tail.next + + return dummy_node.next + + +""" +Solution 2: +Reference: + [1] https://leetcode.com/problems/remove-nth-node-from-end-of-list/editorial/ + [2] https://www.algodale.com/problems/remove-nth-node-from-end-of-list/ +One Pass Algorithm + - Use two pointers to maintain a gap of n nodes between them + - When the first pointer reaches the end, the second pointer will be at the Nth node from the end + +Time complexity: O(N) + - Only one pass is required +Space complexity: O(1) + - No extra space is required +""" + + +class Solution2: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + dummy = ListNode() + dummy.next = head + first = dummy + second = dummy + + for _ in range(n + 1): + first = first.next + + while first: + first = first.next + second = second.next + + second.next = second.next.next + + return dummy.next diff --git a/reorder-list/bhyun-kim.py b/reorder-list/bhyun-kim.py new file mode 100644 index 000000000..d65c634d5 --- /dev/null +++ b/reorder-list/bhyun-kim.py @@ -0,0 +1,75 @@ +""" +143. Reorder List +https://leetcode.com/problems/reorder-list/ +""" + +from typing import Optional + + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + +""" +Solution: + To reorder the linked list, we can follow these steps: + First, find the middle of the linked list using the slow and fast pointers. + Reverse the second half of the linked list. + Merge the first half and the reversed second half. + + 1. Find the middle of the linked list using the slow and fast pointers + - Initialize the slow and fast pointers to the head of the linked list + - Move the slow pointer by one step and the fast pointer by two steps + until the fast pointer reaches the end of the linked list. + 2. Reverse the second half of the linked list + - Initialize the prev and curr pointers to None and the middle node, respectively + - Iterate through the second half of the linked list + - Store the next node of the current node + - Reverse the current node + - Move the prev and curr pointers to the next nodes + 3. Merge the first half and the reversed second half + - Initialize the first and second pointers to the head and the reversed second half, respectively + - Iterate through the linked list + - Store the next nodes of the first and second pointers + - Update the next pointers of the first and second pointers + - Move the first and second pointers to the next nodes + +Time complexity: O(N) + - We iterate through the linked list to find the middle node and reverse the second half + - The time complexity is O(N) + +Space complexity: O(1) + - We only use constant extra space for the pointers + - The space complexity is O(1) + +""" + + +class Solution: + def reorderList(self, head: Optional[ListNode]) -> None: + """ + Do not return anything, modify head in-place instead. + """ + if not head or not head.next: + return + + slow, fast = head, head + while fast and fast.next: + slow = slow.next + fast = fast.next.next + + prev, curr = None, slow + while curr: + next_temp = curr.next + curr.next = prev + prev = curr + curr = next_temp + + first, second = head, prev + while second.next: + tmp1, tmp2 = first.next, second.next + first.next = second + second.next = tmp1 + first, second = tmp1, tmp2 diff --git a/validate-binary-search-tree/bhyun-kim.py b/validate-binary-search-tree/bhyun-kim.py new file mode 100644 index 000000000..dddf7a59d --- /dev/null +++ b/validate-binary-search-tree/bhyun-kim.py @@ -0,0 +1,57 @@ +""" +98. Validate Binary Search Tree +https://leetcode.com/problems/validate-binary-search-tree/ + + +""" +from typing import Optional + + +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + +""" +Solution1: + Recursion + In the given problem, the subtree of a node has a range of values according to the previous nodes. + Thus, we can define a function that checks the validity of the subtree of a node with the range of values. + + - Define a function that checks the validity of the subtree of a node with the range of values + - Check the validity of the left subtree and the right subtree + - with the range of values that the left subtree and the right subtree should have + - If left < root < right, the subtree is valid + - If the left subtree and the right subtree are valid, call the function recursively for the left and right subtrees. + - before calling the function, update the range of values for the left and right subtrees + - If the left subtree and the right subtree are valid, return True + +Time complexity: O(N) + - The function is called recursively for each node + +Space complexity: O(N) + - The function stores the range of values for each node +""" + + +class Solution1: + def isValidBST(self, root: Optional[TreeNode]) -> bool: + maximum = float("inf") + minimum = float("-inf") + return self.isValidSubTree(root, maximum, minimum) + + def isValidSubTree(self, root, maximum, minimum): + + if root is None: + return True + + if not minimum < root.val < maximum: + return False + + return self.isValidSubTree( + root.left, root.val, minimum + ) and self.isValidSubTree( + root.right, maximum, root.val + )