diff --git a/Problem1.py b/Problem1.py new file mode 100644 index 00000000..e026cb0d --- /dev/null +++ b/Problem1.py @@ -0,0 +1,36 @@ +## Problem1 (https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/) + +# Time Complexity: O(N) where N is the length of the input array. +# We iterate through the array twice, doing constant time operations. +# Space Complexity: O(1) auxiliary space. We modify the input array in-place to track +# visited numbers. (The result array does not count towards auxiliary space). + +class Solution: + def findDisappearedNumbers(self, nums: List[int]) -> List[int]: + result = [] + + # First pass: mark the numbers we have seen. + for n in nums: + # Since numbers are 1 to N, we subtract 1 to map the number to a valid 0-based index. + # We use abs(n) because the value might have already been turned negative + # by a previous step in the loop. + idx = abs(n) - 1 + + # If the value at this target index is still positive, we make it negative. + # A negative value at index 'idx' acts as a flag indicating that the number + # 'idx + 1' exists in the array. + if nums[idx] > 0: + nums[idx] *= -1 + + # Second pass: find the missing numbers and restore the original array. + for i, n in enumerate(nums): + # If the value is negative, it means the number (i + 1) was present in the array. + # We restore the array's original state by turning it back to positive. + if n < 0: + nums[i] *= -1 + # If the value is positive, it means we never encountered the number (i + 1) + # during our first pass. Thus, it's a disappeared number. + else: + result.append(i + 1) + + return result \ No newline at end of file diff --git a/Problem2.py b/Problem2.py new file mode 100644 index 00000000..63e9f191 --- /dev/null +++ b/Problem2.py @@ -0,0 +1,64 @@ +## Problem2 +# Given an array of numbers of length N, find both the minimum and maximum. +# Follow up : Can you do it using less than 2 * (N - 2) comparison +# https://www.geeksforgeeks.org/dsa/maximum-and-minimum-in-an-array/ +def findMinMax(nums: List[int]) -> None: + return helper(nums, 0, len(nums) - 1) + +def helper(arr, low, high): + """ + Finds the minimum and maximum elements in an array using a Divide and Conquer approach. + + Logic: + - Divide the array into two halves until reaching a base case (1 or 2 elements). + - Conquer by directly finding the min and max of these small segments. + - Combine the results by comparing the minimums and maximums of the left + and right halves to determine the overall min and max. + + Time Complexity: O(n) + - The recurrence relation is T(n) = 2*T(n/2) + 2 comparisons for the combine step. + - This resolves to O(n). Specifically, it makes roughly 3n/2 - 2 comparisons, + which is more efficient than a naive traversal that makes 2n comparisons. + + Space Complexity: O(log n) + - While no extra arrays are created (O(1) auxiliary data structure space), + the recursive call stack reaches a maximum depth of log(n) frames. + """ + result = [0, 0] + + # Base case 1: If the array segment has only one element. + # No comparison needed; the single element is both the min and the max. + if low == high: + result[0] = arr[low] + result[1] = arr[low] + return result + + # Base case 2: If the array segment has exactly two elements. + # Only 1 comparison is needed to determine the min and max. + if high == low + 1: + if arr[low] < arr[high]: + result[0] = arr[low] + result[1] = arr[high] + else: + result[0] = arr[high] + result[1] = arr[low] + return result + + # Divide step: Calculate the midpoint to split the array into two halves. + mid = (low + high) // 2 + + # Conquer step: Recursively find min and max for the left half. + left = helper(arr, low, mid) + + # Conquer step: Recursively find min and max for the right half. + right = helper(arr, mid + 1, high) + + # Combine step: The overall minimum is the smaller of the two half-minimums. + result[0] = min(left[0], right[0]) + + # Combine step: The overall maximum is the larger of the two half-maximums. + result[1] = max(left[1], right[1]) + + return result + +print(findMinMax([3, 5, 1, 6, 8, 4])) \ No newline at end of file diff --git a/Problem3.py b/Problem3.py new file mode 100644 index 00000000..8afd5386 --- /dev/null +++ b/Problem3.py @@ -0,0 +1,60 @@ +## Problem3 (https://leetcode.com/problems/game-of-life/) + +class Solution: + def gameOfLife(self, board: List[List[int]]) -> None: + """ + Do not return anything, modify board in-place instead. + + Time Complexity: O(M * N), where M is the number of rows and N is the number of columns. + We iterate through the entire board twice, doing a constant amount + of work (checking 8 neighbors) for each cell. + Space Complexity: O(1). We modify the board in-place using temporary state values + (2 and 3) instead of creating a copy of the board. + """ + # Define the 8 possible directions for neighboring cells (horizontal, vertical, diagonal) + directions = [[-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1]] + + rows = len(board) + cols = len(board[0]) + + # We use intermediate states to track changes without losing the original state for neighbors: + # Original 0 -> Becomes 0: keep as 0 + # Original 1 -> Becomes 1: keep as 1 + # Original 1 -> Becomes 0: mark as 2 + # Original 0 -> Becomes 1: mark as 3 + + # First pass: Determine the next state for each cell + for r in range(rows): + for c in range(cols): + element = board[r][c] + liveCount = 0 + + # Count the number of live neighbors + for (rd, cd) in directions: + # Check if the neighbor is within board boundaries + if r + rd in range(rows) and c + cd in range(cols): + # A neighbor is considered "live" if it is currently 1, + # or if it was originally 1 but is marked to die (2). + if board[r + rd][c + cd] == 1 or board[r + rd][c + cd] == 2: + liveCount += 1 + + # Apply Game of Life rules based on the original state (element) + + # Rule 1 & 3: Any live cell with fewer than 2 or more than 3 live neighbors dies. + if element and (liveCount < 2 or liveCount > 3): + board[r][c] = 2 # Mark as 2 (Live -> Dead) + + # Rule 4: Any dead cell with exactly 3 live neighbors becomes a live cell. + if element == 0 and liveCount == 3: + board[r][c] = 3 # Mark as 3 (Dead -> Live) + + # Note: Rule 2 (Live cell with 2 or 3 live neighbors lives on) requires no action + # because the cell remains 1. + + # Second pass: Finalize the board by updating intermediate states to their final values + for r in range(rows): + for c in range(cols): + if board[r][c] == 2: + board[r][c] = 0 # Finalize Live -> Dead + if board[r][c] == 3: + board[r][c] = 1 # Finalize Dead -> Live \ No newline at end of file