Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Problem1.py
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions Problem2.py
Original file line number Diff line number Diff line change
@@ -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]))
60 changes: 60 additions & 0 deletions Problem3.py
Original file line number Diff line number Diff line change
@@ -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