diff --git a/Problem1.py b/Problem1.py new file mode 100644 index 00000000..020b315e --- /dev/null +++ b/Problem1.py @@ -0,0 +1,22 @@ +# Problem1 (https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/) +# TC = O(n) +# SC = O(1) +#Did this code successfully run on Leetcode : Yes +# Use the array itself to mark which numbers exist: for each value seen, +# flip the number at its corresponding index (value-1) negative, +# then any index still positive after that means its corresponding number (index+1) was never found. + +class Solution: + def findDisappearedNumbers(self, nums: List[int]) -> List[int]: + n = len(nums) + result = [] + for i in range(n): # marking pass: go through every index in the array + index = abs(nums[i]) - 1 # convert this value into the index it "belongs to" (use abs() since it may already be flipped negative) + if nums[index] > 0: # check if that target index hasn't been marked yet + nums[index] *= -1 # flip it negative to record "this number exists" + + for i in range(n): # checking pass: go through every index one more time + if nums[i] > 0: # still positive means nothing ever pointed to this index + result.append(i + 1) # convert index back to value and mark it missing + + return result \ No newline at end of file diff --git a/Problem2.py b/Problem2.py new file mode 100644 index 00000000..5b223fe7 --- /dev/null +++ b/Problem2.py @@ -0,0 +1,40 @@ +#https://www.geeksforgeeks.org/dsa/maximum-and-minimum-in-an-array/ +# TC = O(n) +# SC = O(1) +# Process the array two elements at a time: compare each pair against each other first, +# then only check the smaller one against mini and the larger one against maxi, +# cutting comparisons from 4 to 3 per pair. + +def find_min_max(arr): + n = len(arr) + if n % 2 == 1: # check if array length is odd + mini = maxi = arr[0] # seed both mini and maxi with the first element + i = 1 # start pairing loop from index 1, since index 0 is already used + else: + if arr[0] < arr[1]: # compare the first two elements to seed mini/maxi + mini = arr[0] # arr[0] is smaller, so it becomes the initial min + maxi = arr[1] # arr[1] is larger, so it becomes the initial max + else: + mini = arr[1] # arr[1] is smaller, so it becomes the initial min + maxi = arr[0] # arr[0] is larger, so it becomes the initial max + i = 2 # start pairing loop from index 2, since indices 0 and 1 are already used + + while i < n - 1: # loop while a full pair (i and i+1) still exists in bounds + if arr[i] < arr[i + 1]: # compare the pair against each other + mini = min(arr[i], mini) # arr[i] is the smaller one, check it against current min + maxi = max(arr[i + 1], maxi) # arr[i+1] is the larger one, check it against current max + else: + mini = min(arr[i + 1], mini) + maxi = max(arr[i], maxi) + i += 2 # move forward by 2 to process the next pair + + return [mini, maxi] + + +def main(): + arr = [3, 5, 4, 1, 9] + result = find_min_max(arr) + print(result[0], result[1]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Problem3.py b/Problem3.py new file mode 100644 index 00000000..20bee5d1 --- /dev/null +++ b/Problem3.py @@ -0,0 +1,53 @@ +#https://leetcode.com/problems/game-of-life/ +# Time Complexity: O(m * n) -> we visit every cell once in pass 1 (each visit checks 8 neighbors, a constant) then every cell once again in pass 2, so total work scales with grid size +# Space Complexity: O(1) +# Use 2 extra encoded states (2 = live->dying, 3 = dead->being born) so we can read the ORIGINAL generation's values while deciding the NEXT generation, without needing a second grid. +# Pass 1 scans every cell, counts live neighbors using getCount(), and marks transitions (2 or 3) based on the 4 Game of Life rules, without overwriting the original 0/1 meaning yet. +# Pass 2 converts the encoded markers back to real 0/1 values, giving the final next-generation board. +# list of all 8 neighbor offsets (dx = row shift, dy = column shift) around any cell excludes (0,0) since that would be the cell itself, not a neighbor + + + +class Solution: + def gameOfLife(self, board: List[List[int]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + directions = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] + + m = len(board) + n = len(board[0]) + # helper function: counts how many of cell (i, j)'s neighbors are CURRENTLY alive + # i = row index of the cell we're checking neighbors for + # j = column index of the cell we're checking neighbors for + def getCount(i, j): + count = 0 # running tally of live neighbors found so far, starts at 0 + for dx, dy in directions: # loop through all 8 direction offsets one at a time + row, column = i + dx, j + dy # row, column = the neighbor's actual position on the board + if 0 <= row < m and 0 <= column < n: # boundary check: skip if neighbor would be off the grid + # a value of 1 = currently alive, 2 = currently alive but marked to die next round + # both count as "alive right now", which is what we care about for this generation + if board[row][column] == 1 or board[row][column] == 2: + count += 1 # found a live neighbor, increment the tally + return count + # PASS 1: decide every cell's fate based on the ORIGINAL board values, but don't apply changes yet + for i in range(m): + for j in range(n): + finalcount = getCount(i, j) # finalcount = number of live neighbors for cell (i, j) + + # Rule 4: cell is currently dead AND has exactly 3 live neighbors + if board[i][j] == 0 and finalcount == 3: + board[i][j] = 3 + + # Rules 1 & 3 (under/over-population): cell is currently alive AND has too few/too many neighbors + elif board[i][j] == 1 and (finalcount < 2 or finalcount > 3): + board[i][j] = 2 + # NOTE: Rule 2 (2 or 3 neighbors -> stays alive) needs no code; the cell just remains 1 untouched + + # PASS 2: convert all placeholder markers into their real final 0/1 values + for i in range(m): + for j in range(n): + if board[i][j] == 2: + board[i][j] = 0 + elif board[i][j] == 3: + board[i][j] = 1 \ No newline at end of file