Complete solutions with explanations, step-by-step breakdowns, and complexity analysis.
Problem: Return the sum of all numbers in a 2D grid.
Use nested loops to visit every cell and accumulate the sum.
total = 0def grid_sum(grid): total = 0 for row in grid: for num in row: total += num return total
def grid_sum_alternative(grid): return sum(sum(row) for row in grid)
O(rows Ć cols) - Must visit every cell once
O(1) - Only using a single variable for accumulation
print(grid_sum([[1, 2, 3], [4, 5, 6]])) # Output: 21 print(grid_sum([[10, -5], [3, 7], [2, -1]])) # Output: 16
Problem: Return the largest number in the entire grid.
Track the maximum value while scanning all cells with nested loops.
max_val to the first element grid[0][0]max_val, update max_valmax_valdef grid_maximum(grid): max_val = grid[0][0] # Start with first element for row in grid: for num in row: if num > max_val: max_val = num return max_val
def grid_maximum_alternative(grid): return max(max(row) for row in grid)
O(rows Ć cols) - Must scan every cell to find maximum
O(1) - Only tracking one variable
print(grid_maximum([[3, 7, 2], [9, 1, 5], [4, 8, 6]])) # Output: 9 print(grid_maximum([[-5, -2], [-10, -1], [-3, -7]])) # Output: -1
Problem: Count how many times a target value appears in the grid.
Scan every cell and count matches to the target value.
count = 0def count_value(grid, target): count = 0 for row in grid: for num in row: if num == target: count += 1 return count
O(rows Ć cols) - Must check every cell
O(1) - Only using a counter variable
grid = [[1, 2, 1], [3, 1, 2], [1, 3, 1]] print(count_value(grid, 1)) # Output: 5 print(count_value(grid, 3)) # Output: 2 print(count_value(grid, 5)) # Output: 0
Problem: Return a tuple containing (list of row sums, list of column sums).
rows = len(grid), cols = len(grid[0])row_sumsrow_sumscol_sumscol:
col_sum = 0row, add grid[row][col] to col_sumcol_sum to col_sums(row_sums, col_sums)def sum_rows_and_cols(grid): rows = len(grid) cols = len(grid[0]) # Calculate row sums row_sums = [] for row in grid: row_sums.append(sum(row)) # Calculate column sums col_sums = [] for col in range(cols): col_sum = 0 for row in range(rows): col_sum += grid[row][col] col_sums.append(col_sum) return (row_sums, col_sums)
O(rows Ć cols) - Visit each cell once for row sums, once for column sums
O(rows + cols) - Storing two lists (row sums and column sums)
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print(sum_rows_and_cols(grid)) # Output: ([6, 15, 24], [12, 15, 18]) grid2 = [[1, 1], [2, 2], [3, 3]] print(sum_rows_and_cols(grid2)) # Output: ([2, 4, 6], [6, 6])
Problem: Return the position (row, col) of the first occurrence of target. Return None if not found.
Scan the grid left-to-right, top-to-bottom until the target is found.
len(grid)len(grid[0])grid[row][col] equals target, return (row, col)Nonedef find_position(grid, target): for row in range(len(grid)): for col in range(len(grid[0])): if grid[row][col] == target: return (row, col) return None
O(rows Ć cols) - Worst case: target is last cell or doesn't exist
O(1) - No additional data structures needed
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print(find_position(grid, 5)) # Output: (1, 1) print(find_position(grid, 7)) # Output: (2, 0) print(find_position(grid, 10)) # Output: None
Problem: Count how many of the 4 adjacent cells (up, down, left, right) equal a target value.
Check all 4 directions from the given position, counting matches while respecting grid boundaries.
rows, colscount = 0[(-1, 0), (1, 0), (0, -1), (0, 1)] (up, down, left, right)(dr, dc):
new_row = row + dr, new_col = col + dc0 <= new_row < rows and 0 <= new_col < colsgrid[new_row][new_col] == value, increment countdef count_neighbors(grid, row, col, value): rows = len(grid) cols = len(grid[0]) count = 0 # Define 4 directions: up, down, left, right directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] for dr, dc in directions: new_row = row + dr new_col = col + dc # Check if within bounds if 0 <= new_row < rows and 0 <= new_col < cols: if grid[new_row][new_col] == value: count += 1 return count
O(1) - Always checks exactly 4 neighbors (constant time)
O(1) - Fixed number of variables
grid = [[1, 2, 1], [2, 5, 2], [1, 2, 1]] print(count_neighbors(grid, 1, 1, 2)) # Output: 4 print(count_neighbors(grid, 0, 0, 2)) # Output: 1 print(count_neighbors(grid, 1, 1, 1)) # Output: 0
Problem: Print numbers from n down to 1 using recursion (no loops).
Use recursion with a base case to stop at 0.
n == 0, return (stop recursing)ncountdown(n - 1) to handle remaining numbersdef countdown(n): if n == 0: # Base case return print(n) countdown(n - 1) # Recursive call
countdown(3)
print(3)
āā countdown(2)
print(2)
āā countdown(1)
print(1)
āā countdown(0)
return (base case)
O(n) - Makes n recursive calls
O(n) - Call stack depth is n
countdown(5) # Output: # 5 # 4 # 3 # 2 # 1
Problem: Return the sum of all elements from a given index to the end of the list using recursion.
Use recursion to break down the problem: sum = current element + sum of remaining elements.
index >= len(numbers), return 0 (no more elements)numbers[index] + recursive_sum(numbers, index + 1)def recursive_sum(numbers, index): if index >= len(numbers): # Base case return 0 # Recursive case: current element + sum of remaining elements return numbers[index] + recursive_sum(numbers, index + 1)
recursive_sum([1,2,3], 0)
return 1 + recursive_sum([1,2,3], 1)
return 2 + recursive_sum([1,2,3], 2)
return 3 + recursive_sum([1,2,3], 3)
return 0 (base case)
= 3 + 0 = 3
= 2 + 3 = 5
= 1 + 5 = 6
O(n) - Makes n recursive calls where n = length - index
O(n) - Call stack depth proportional to number of elements
print(recursive_sum([1, 2, 3, 4, 5], 0)) # Output: 15 print(recursive_sum([1, 2, 3, 4, 5], 2)) # Output: 12 (3+4+5) print(recursive_sum([10, 20, 30], 1)) # Output: 50 (20+30)
Problem: Change a cell and all connected cells of the same color to a new color (like paint bucket tool).
Use DFS recursion to spread the color change to all connected cells.
original_color = grid[row][col]original_color == new_color, return grid (nothing to change)fill(r, c):
grid[r][c] to new_colorfill() on all 4 neighborsfill(row, col) to start the processdef flood_fill(grid, row, col, new_color): rows = len(grid) cols = len(grid[0]) original_color = grid[row][col] # If already the target color, nothing to do if original_color == new_color: return grid def fill(r, c): # Base cases if r < 0 or r >= rows or c < 0 or c >= cols: return if grid[r][c] != original_color: return # Change color grid[r][c] = new_color # Recursively fill neighbors fill(r - 1, c) # up fill(r + 1, c) # down fill(r, c - 1) # left fill(r, c + 1) # right fill(row, col) return grid
Before flood_fill(grid, 0, 0, 2):
1 1 1
1 0 1
1 1 1
After:
2 2 2
2 0 2
2 2 2
O(rows Ć cols) - Worst case: fill entire grid
O(rows Ć cols) - Call stack depth in worst case (entire grid connected)
grid = [[1, 1, 1], [1, 0, 1], [1, 1, 1]] result = flood_fill(grid, 0, 0, 2) # Result: [[2, 2, 2], [2, 0, 2], [2, 2, 2]] grid2 = [[1, 1, 0], [1, 0, 0], [0, 0, 1]] result2 = flood_fill(grid2, 0, 0, 5) # Result: [[5, 5, 0], [5, 0, 0], [0, 0, 1]]
Problem: Count how many separate regions of a target value exist in the grid.
Use DFS to mark entire regions, counting each time we start a new DFS.
visited grid (all False initially)region_count = 0region_countregion_countdef count_regions(grid, target): if not grid: return 0 rows = len(grid) cols = len(grid[0]) visited = [[False] * cols for _ in range(rows)] region_count = 0 def dfs(row, col): # Base cases if (row < 0 or row >= rows or col < 0 or col >= cols or visited[row][col] or grid[row][col] != target): return # Mark as visited visited[row][col] = True # Explore all 4 neighbors dfs(row - 1, col) # up dfs(row + 1, col) # down dfs(row, col - 1) # left dfs(row, col + 1) # right # Scan entire grid for row in range(rows): for col in range(cols): if grid[row][col] == target and not visited[row][col]: dfs(row, col) # Mark entire region region_count += 1 return region_count
Grid:
1 1 0 0
1 0 0 1
0 0 1 1
Regions of 1:
Region 1: (0,0), (0,1), (1,0)
Region 2: (1,3)
Region 3: (2,2), (2,3)
Total: 3 regions
O(rows Ć cols) - Visit each cell at most twice (once in main loop, once in DFS)
O(rows Ć cols) - Visited array + call stack in worst case
grid1 = [[1, 1, 0, 0], [1, 0, 0, 1], [0, 0, 1, 1]] print(count_regions(grid1, 1)) # Output: 3 grid2 = [[2, 2, 3], [2, 3, 3], [3, 3, 3]] print(count_regions(grid2, 3)) # Output: 1 print(count_regions(grid2, 2)) # Output: 1
Problem: Determine if a path exists from current position to bottom-right corner.
Use DFS recursion to explore all possible paths. Return True if ANY path reaches the destination.
(rows-1, cols-1) and it's a path (1), return Truedef has_path(maze, row, col, visited): rows = len(maze) cols = len(maze[0]) # Base case: reached destination if row == rows - 1 and col == cols - 1: return maze[row][col] == 1 # Must be a valid path cell # Base cases: invalid position if (row < 0 or row >= rows or col < 0 or col >= cols or maze[row][col] == 0 or (row, col) in visited): return False # Mark as visited visited.add((row, col)) # Try all 4 directions if has_path(maze, row - 1, col, visited): # up return True if has_path(maze, row + 1, col, visited): # down return True if has_path(maze, row, col - 1, visited): # left return True if has_path(maze, row, col + 1, visited): # right return True # No path found from this cell return False
Maze:
1 0 1
1 1 1
0 0 1
Path exists: (0,0)ā(1,0)ā(1,1)ā(1,2)ā(2,2) ā
O(rows Ć cols) - Visit each cell at most once
O(rows Ć cols) - Visited set + recursion call stack
maze1 = [[1, 0, 1], [1, 1, 1], [0, 0, 1]] visited1 = set() print(has_path(maze1, 0, 0, visited1)) # Output: True maze2 = [[1, 0, 1], [0, 0, 1], [0, 0, 1]] visited2 = set() print(has_path(maze2, 0, 0, visited2)) # Output: False
Problem: Find the length of the shortest path from top-left to bottom-right. Return -1 if no path exists.
Use BFS with a queue to explore level-by-level, guaranteeing the shortest path.
deque([(0, 0, 1)]) (row, col, distance){(0, 0)}row, col, dist = queue.popleft()dist(new_row, new_col, dist + 1)from collections import deque def shortest_path_length(maze): if not maze or maze[0][0] == 0: return -1 rows = len(maze) cols = len(maze[0]) # BFS setup queue = deque([(0, 0, 1)]) # (row, col, distance) visited = {(0, 0)} while queue: row, col, dist = queue.popleft() # Check if reached destination if row == rows - 1 and col == cols - 1: return dist # Explore all 4 neighbors for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]: new_row = row + dr new_col = col + dc # Check if valid and unvisited if (0 <= new_row < rows and 0 <= new_col < cols and maze[new_row][new_col] == 1 and (new_row, new_col) not in visited): visited.add((new_row, new_col)) queue.append((new_row, new_col, dist + 1)) return -1 # No path found
Maze:
1 1 0
0 1 0
0 1 1
BFS explores in waves (by distance):
Distance 1: (0,0)
Distance 2: (0,1)
Distance 3: (1,1)
Distance 4: (2,1)
Distance 5: (2,2) ā Destination reached!
BFS explores cells in order of increasing distance from the start. The first time we reach the destination is guaranteed to be via the shortest path.
O(rows Ć cols) - Visit each cell at most once
O(rows Ć cols) - Queue and visited set can contain all cells
maze1 = [[1, 1, 0], [0, 1, 0], [0, 1, 1]] print(shortest_path_length(maze1)) # Output: 5 maze2 = [[1, 0, 1], [1, 1, 1], [0, 0, 1]] print(shortest_path_length(maze2)) # Output: 5 maze3 = [[1, 0], [0, 1]] print(shortest_path_length(maze3)) # Output: -1
1. Full Scan (Nested Loops)
for row in range(rows): for col in range(cols): # Process grid[row][col]
2. DFS Recursion (Flood Fill, Connected Components)
def dfs(row, col): if out_of_bounds or invalid or visited: return visited.add((row, col)) dfs(row-1, col) # up dfs(row+1, col) # down dfs(row, col-1) # left dfs(row, col+1) # right
3. BFS with Queue (Shortest Path)
queue = deque([(start_row, start_col, 0)]) visited = {(start_row, start_col)} while queue: row, col, dist = queue.popleft() for each neighbor: if valid and unvisited: queue.append((new_row, new_col, dist+1))
ā Base Case - When to stop (prevents infinite recursion) ā Recursive Case - How to break down the problem ā Progress - Each call must get closer to base case
| Feature | DFS | BFS |
|---|---|---|
| Implementation | Recursion or Stack | Queue |
| Exploration | Deep first | Level by level |
| Best for | Any path, all paths | Shortest path |
| Space | O(depth) | O(width) |
Always validate before accessing grid:
if 0 <= row < rows and 0 <= col < cols: # Safe to access grid[row][col]
ā Forgetting base case in recursion ā infinite loop ā Not checking boundaries ā index out of range ā Not tracking visited cells ā infinite loops in DFS ā Using DFS when shortest path needed ā use BFS instead ā Modifying grid during iteration without copying
visited, row, col better than v, i, jGood luck with CCC Question 5! š