diff --git a/longest-substring-without-repeating-characters/alphaorderly.py b/longest-substring-without-repeating-characters/alphaorderly.py new file mode 100644 index 0000000000..32bec2cfbb --- /dev/null +++ b/longest-substring-without-repeating-characters/alphaorderly.py @@ -0,0 +1,26 @@ +""" +Time Complexity: O(n) +Space Complexity: O(n) + +Approach: +- Use a defaultdict to track the count of each character in the current window. +- Maintain two pointers, 'left' and 'right', to represent the sliding window over the string. +- As we iterate over the string with 'right', increment the count for the current character. +- If a duplicate character appears in the window (count > 1), move the 'left' pointer forward and decrement counts until there are no duplicates. +- After adjusting, update 'ans' with the maximum length found for a window with all unique characters. +""" +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + count = defaultdict(int) + ans = left = 0 + + for right, val in enumerate(s): + count[val] += 1 + + while count[val] > 1: + count[s[left]] -= 1 + left += 1 + + ans = max(ans, right - left + 1) + + return ans diff --git a/number-of-islands/alphaorderly.py b/number-of-islands/alphaorderly.py new file mode 100644 index 0000000000..9d01bcbf39 --- /dev/null +++ b/number-of-islands/alphaorderly.py @@ -0,0 +1,87 @@ +""" +Time Complexity: O(m * n) +Space Complexity: O(m * n) + +### BFS Approach ### + +Approach: +- Use BFS to traverse all parts of each island in the grid. +- Employ a queue to process all adjacent land cells iteratively. +- Use a 'bound' helper function to check if a cell is within the grid bounds. +- In 'island_marker', mark visited '1's with '#' to avoid revisiting. +- For every cell in the grid, when a land cell ('1') is encountered, initiate BFS and increment the island count. +""" +class Solution: + def numIslands(self, grid: List[List[str]]) -> int: + DIR = [[0, 1], [1, 0], [-1, 0], [0, -1]] + ROW = len(grid) + COL = len(grid[0]) + + def bound(row: int, col: int) -> bool: + return 0 <= row < ROW and 0 <= col < COL + + def island_marker(row: int, col: int) -> None: + q = deque([(row, col)]) + grid[row][col] = "#" + + while q: + r, c = q.popleft() + + for dr, dc in DIR: + tr, tc = r + dr, c + dc + + if not bound(tr, tc) or grid[tr][tc] != "1": + continue + + grid[tr][tc] = "#" + q.append((tr, tc)) + + ans = 0 + + for r in range(ROW): + for c in range(COL): + if grid[r][c] == "1": + island_marker(r, c) + ans += 1 + + return ans + +""" +Time Complexity: O(m * n) +Space Complexity: O(m * n) + +### DFS Approach ### + +Approach: +- Use DFS to traverse all parts of each island in the grid. +- Visitation is done recursively rather than with a stack, so stack comment is removed for clarity. +- Use a 'bound' helper function to check if a cell is within the grid bounds. +- In 'island_marker', mark visited '1's with '#' to avoid revisiting. +- For every cell in the grid, when a land cell ('1') is encountered, initiate DFS and increment the island count. +""" +class Solution: + def numIslands(self, grid: List[List[str]]) -> int: + DIR = [[0, 1], [1, 0], [-1, 0], [0, -1]] + ROW = len(grid) + COL = len(grid[0]) + + def bound(row: int, col: int) -> bool: + return 0 <= row < ROW and 0 <= col < COL + + def island_marker(row: int, col: int) -> None: + grid[row][col] = '#' + + for dr, dc in DIR: + tr, tc = row + dr, col + dc + if bound(tr, tc) and grid[tr][tc] == '1': + island_marker(tr, tc) + + ans = 0 + + for r in range(ROW): + for c in range(COL): + if grid[r][c] == "1": + island_marker(r, c) + ans += 1 + + return ans diff --git a/reverse-linked-list/alphaorderly.py b/reverse-linked-list/alphaorderly.py new file mode 100644 index 0000000000..66312e7b51 --- /dev/null +++ b/reverse-linked-list/alphaorderly.py @@ -0,0 +1,25 @@ +""" +Time Complexity: O(n) +Space Complexity: O(1) + +- We use a while loop to traverse the linked list. +- We use a prev pointer to store the previous node. +- We use a head pointer to store the current node. +- We use a old_next pointer to store the next node. +- We use a prev, head = head, old_next to update the prev and head pointers. +- We return the prev pointer. +""" + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + prev = None + + while head: + prev, head.next, head = head, prev, head.next + + return prev diff --git a/set-matrix-zeroes/alphaorderly.py b/set-matrix-zeroes/alphaorderly.py new file mode 100644 index 0000000000..ed1962e550 --- /dev/null +++ b/set-matrix-zeroes/alphaorderly.py @@ -0,0 +1,37 @@ +""" +Time Complexity: O(m * n) +Space Complexity: O(1) + +Approach: +- Use the first row and first column as markers to track which rows and columns should be zeroed. +- First, check if the original first row or first column should be zeroed by scanning them separately. +- Then, scan the rest of the matrix. If an element is zero, set its corresponding first row and first column positions to zero. +- Next, iterate through the matrix (excluding the first row and column) and set elements to zero if their corresponding first row or first column are zero. +- Finally, zero the first row and/or first column if initially flagged. +""" +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + ROW = len(matrix) + COL = len(matrix[0]) + + row_check = any(matrix[0][c] == 0 for c in range(COL)) + col_check = any(matrix[r][0] == 0 for r in range(ROW)) + + for r in range(ROW): + for c in range(COL): + if matrix[r][c] == 0: + matrix[r][0] = 0 + matrix[0][c] = 0 + + for r in range(1, ROW): + for c in range(1, COL): + if matrix[r][0] == 0 or matrix[0][c] == 0: + matrix[r][c] = 0 + + if row_check: + for c in range(COL): + matrix[0][c] = 0 + + if col_check: + for r in range(ROW): + matrix[r][0] = 0 diff --git a/unique-paths/alphaorderly.py b/unique-paths/alphaorderly.py new file mode 100644 index 0000000000..fa0a21cadc --- /dev/null +++ b/unique-paths/alphaorderly.py @@ -0,0 +1,56 @@ +""" +Time Complexity: O(m * n) +Space Complexity: O(m * n) + +Dynamic Programming (2D DP approach): +- Use a 2D array where maze[i][j] represents the number of unique paths to cell (i, j). +- Initialize the first row and first column with 1 (since there's only one way to reach each cell: only right moves for the first row or only down moves for the first column). +- For all other cells, maze[i][j] = maze[i-1][j] + maze[i][j-1] (sum of paths from the cell above and the cell to the left). +- Return maze[m-1][n-1] as the answer, which is the total number of unique paths. +""" +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + maze = [[0 for _ in range(n)] for _ in range(m)] + + for i in range(m): + for j in range(n): + if i == 0 or j == 0: + maze[i][j] = 1 + else: + maze[i][j] = maze[i-1][j] + maze[i][j-1] + + return maze[m-1][n-1] + +""" +Time Complexity: O(m * n) +Space Complexity: O(n) + +Dynamic Programming (1D DP optimization): +- Use a 1D array dp of size n. +- dp[c] keeps track of the number of unique paths to column c in the current row. +- Initialize dp with 1s (the first row has only one way to reach each column). +- For every row from the second onward, update dp[c] = dp[c] + dp[c - 1] (add ways from the left neighbor to ways accumulated so far). +- Return dp[-1] as the answer, representing the number of unique paths to the bottom-right cell. +""" +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + dp = [1] * n + + for _ in range(1, m): + for c in range(1, n): + dp[c] += dp[c - 1] + + return dp[-1] + +""" +Time Complexity: O(m + n) +Space Complexity: O(1) + +Combinatorial approach: +- The problem reduces to choosing (m-1) moves down from (m+n-2) total movements (or equivalently (n-1) moves right). +- The number of unique paths is given by the formula (m+n-2)! / [(m-1)! * (n-1)!], representing all possible orderings of down and right moves. +- Use the combinatorial (factorial) formula to compute the result efficiently. +""" +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + return comb(m + n - 2, n - 1)