diff --git a/find-minimum-in-rotated-sorted-array/yolophg.py b/find-minimum-in-rotated-sorted-array/yolophg.py new file mode 100644 index 000000000..3fb18de13 --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/yolophg.py @@ -0,0 +1,22 @@ +# Time Complexity: O(log n) - using binary search, so cut the search space in half each time. +# Space Complexity: O(1) - only use a few variables (low, high, mid), no extra space. + +class Solution: + def findMin(self, nums: List[int]) -> int: + low = 0 + # start with the full range of the array + high = len(nums) - 1 + + # find the middle index + while low < high: + mid = low + (high - low) // 2 + + # if mid is greater than the last element, the min must be on the right + if nums[mid] > nums[high]: + # move the low pointer to the right + low = mid + 1 + else: + # min could be mid or in the left part + high = mid + # low and high converge to the minimum element + return nums[low] diff --git a/linked-list-cycle/yolophg.py b/linked-list-cycle/yolophg.py new file mode 100644 index 000000000..4d8f9a8f3 --- /dev/null +++ b/linked-list-cycle/yolophg.py @@ -0,0 +1,21 @@ +# Time Complexity: O(n) - traverse the linked list at most once. +# Space Complexity: O(1) - only use two pointers, no extra memory. + +class Solution: + def hasCycle(self, head: Optional[ListNode]) -> bool: + # two pointers for fast moves twice as fast as slow. + fast = head + slow = head + + # loop the list while fast and fast.next exist. + while fast and fast.next: + # move fast pointer two steps. + fast = fast.next.next + # move slow pointer one step. + slow = slow.next + + # if they meet, there's a cycle. + if fast == slow: + return True + # if they don't meet, there's no cycle. + return False diff --git a/maximum-product-subarray/yolophg.py b/maximum-product-subarray/yolophg.py new file mode 100644 index 000000000..c493c51a0 --- /dev/null +++ b/maximum-product-subarray/yolophg.py @@ -0,0 +1,25 @@ +# Time Complexity: O(N) - just one pass through the array, so it's linear time. +# Space Complexity: O(1) - no extra arrays, just a few variables. + +class Solution: + def maxProduct(self, nums: List[int]) -> int: + # tracking max product from both ends + prefix_product, suffix_product = 1, 1 + # start with the biggest single number + max_product = max(nums) + + for i in range(len(nums)): + # move forward, multiplying + prefix_product *= nums[i] + # move backward, multiplying + suffix_product *= nums[len(nums) - i - 1] + # update max product + max_product = max(max_product, prefix_product, suffix_product) + + # if hit zero, reset to 1 (zero kills the product chain) + if prefix_product == 0: + prefix_product = 1 + if suffix_product == 0: + suffix_product = 1 + + return max_product diff --git a/minimum-window-substring/yolophg.py b/minimum-window-substring/yolophg.py new file mode 100644 index 000000000..2847c1f50 --- /dev/null +++ b/minimum-window-substring/yolophg.py @@ -0,0 +1,40 @@ +# Time Complexity: O(N) - go through the string with two pointers, so it's basically O(N). +# Space Complexity: O(1) - only storing character frequencies (max 52 keys for a-z & A-Z), so it's effectively constant space. + +class Solution: + def minWindow(self, s: str, t: str) -> str: + # store character counts for t + target_count = Counter(t) + # window character count + window_count = defaultdict(int) + + # start of the window + left = 0 + # min substring + min_substring = "" + # tracks how many characters match the required count + matched_chars = 0 + # unique characters needed + required_chars = len(target_count) + + for right, char in enumerate(s): + # expand window by adding the rightmost character + if char in target_count: + window_count[char] += 1 + if window_count[char] == target_count[char]: + matched_chars += 1 + + # try shrinking the window if all required characters are present + while matched_chars == required_chars: + # update min substring if this one is shorter + if min_substring == "" or (right - left + 1) < len(min_substring): + min_substring = s[left:right + 1] + + # remove leftmost character and move left pointer + if s[left] in window_count: + window_count[s[left]] -= 1 + if window_count[s[left]] < target_count[s[left]]: + matched_chars -= 1 + left += 1 + + return min_substring diff --git a/pacific-atlantic-water-flow/yolophg.py b/pacific-atlantic-water-flow/yolophg.py new file mode 100644 index 000000000..4c92a1071 --- /dev/null +++ b/pacific-atlantic-water-flow/yolophg.py @@ -0,0 +1,44 @@ +# Time Complexity: O(m * n) - running DFS from each border, so worst case, we visit each cell twice. +# Space Complexity: O(m * n) - using two sets to track which cells can reach each ocean and the recursion stack. + +class Solution: + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + + rows = len(heights) + cols = len(heights[0]) + result = [] + + # tracking which cells can reach each ocean + pac, atl = set(), set() + + def dfs(r, c, visited, preHeight): + # if out of bounds, already visited, or can't flow from prev height → just dip + if (r < 0 or c < 0 or r == rows or c == cols or + (r, c) in visited or heights[r][c] < preHeight): + return + + # mark as visited + visited.add((r, c)) + + # go in all 4 directions + dfs(r + 1, c, visited, heights[r][c]) # down + dfs(r - 1, c, visited, heights[r][c]) # up + dfs(r, c + 1, visited, heights[r][c]) # right + dfs(r, c - 1, visited, heights[r][c]) # left + + # hit up all border cells for both oceans + for c in range(cols): + dfs(0, c, pac, heights[0][c]) # top row (pacific) + dfs(rows - 1, c, atl, heights[rows - 1][c]) # bottom row (atlantic) + + for r in range(rows): + dfs(r, 0, pac, heights[r][0]) # leftmost col (pacific) + dfs(r, cols - 1, atl, heights[r][cols - 1]) # rightmost col (atlantic) + + # now just check which cells are in both sets + for r in range(rows): + for c in range(cols): + if (r, c) in pac and (r, c) in atl: + result.append([r, c]) + + return result