DFS/Backtracking Algorithm
Overview
The template for backtracking is as follows:
result = []
def backtrack(path, choice_list):
if end_condition_met:
result.add(path)
return
for choice in choice_list:
# make a choice
backtrack(path, choice_list)
# !!! undo
Permutations (Leetcode 46)
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
self.results = []
tracks = []
used = [False] * len(nums)
self.backtrack(nums, tracks, used)
return self.results
def backtrack(self, nums, tracks, used):
if len(tracks) == len(nums):
self.results.append(tracks.copy())
return
for i in range(len(nums)):
if used[i]:
continue
tracks.append(nums[i])
used[i] = True
self.backtrack(nums, tracks, used)
tracks.pop()
used[i] = False
Sudoku and N-Queens
Sudoku Solver (Leetcode 37)
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
n_rows, n_cols = 9, 9
def isValid(r, c, num):
for i in range(9):
if board[i][c] == num or board[r][i] == num:
return False
start_row, start_col = (r // 3) * 3, (c // 3) * 3
for i in range(3):
for j in range(3):
if board[start_row + i][start_col + j] == num:
return False
return True
def backtrack(index):
if index == 81:
return True
r, c = index // 9, index % 9
if board[r][c] != '.':
return backtrack(index + 1)
for ch in '123456789':
if isValid(r, c, ch):
board[r][c] = ch
if backtrack(index + 1):
return True
board[r][c] = '.'
return False
backtrack(0)
N-Queens (Leetcode 51)
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
self.res = []
board = ['.' * n for _ in range(n)]
def isValid(board, row, col):
n = len(board)
for i in range(row):
if board[i][col] == 'Q':
return False
for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
if board[i][j] == 'Q':
return False
for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
if board[i][j] == 'Q':
return False
return True
def backtrack(board, row):
if row == n:
self.res.append(board[:])
return
for col in range(n):
if not isValid(board, row, col):
continue
board[row] = board[row][:col] + 'Q' + board[row][col+1:]
backtrack(board, row+1)
board[row] = board[row][:col] + '.' + board[row][col+1:]
backtrack(board, 0)
return self.res
N-Queens II (Leetcode 52)
class Solution:
def totalNQueens(self, n: int) -> int:
self.res = 0
board = ['.' * n for _ in range(n)]
def isValid(board, row, col):
n = len(board)
for i in range(row):
if board[i][col] == 'Q':
return False
for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)):
if board[i][j] == 'Q':
return False
for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)):
if board[i][j] == 'Q':
return False
return True
def backtrack(board, row):
if row == n:
self.res += 1
return
for col in range(n):
if not isValid(board, row, col):
continue
board[row] = board[row][:col] + 'Q' + board[row][col+1:]
backtrack(board, row+1)
board[row] = board[row][:col] + '.' + board[row][col+1:]
backtrack(board, 0)
return self.res
Permutations & Combinations & Subsets
Subsets (Leetcode 78)
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
self.res = []
tracks = []
start = 0
self.backtrack(nums, tracks, start)
return self.res
def backtrack(self, nums, tracks, start):
self.res.append(tracks.copy())
for i in range(start, len(nums)):
tracks.append(nums[i])
self.backtrack(nums, tracks, i+1)
tracks.pop()
Combinations (Leetcode 77)
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
self.res = []
tracks = []
start = 0
nums = range(1, n+1)
self.backtrack(nums, tracks, start, k)
return self.res
def backtrack(self, nums, tracks, start, k):
if len(tracks) == k:
self.res.append(tracks.copy())
return
for i in range(start, len(nums)):
tracks.append(nums[i])
self.backtrack(nums, tracks, i+1, k)
tracks.pop()
Subsets II (Leetcode 90)
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
self.res = []
tracks = []
start = 0
nums.sort()
self.backtrack(nums, tracks, start)
return self.res
def backtrack(self, nums, tracks, start):
self.res.append(tracks.copy())
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]:
continue
tracks.append(nums[i])
self.backtrack(nums, tracks, i+1)
tracks.pop()
Combination Sum II (Leetcode 40)
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
self.res = []
tracks = []
candidates.sort()
self.backtrack(candidates, tracks, target, 0)
return self.res
def backtrack(self, candidates, tracks, target, start):
if target == 0:
self.res.append(tracks.copy())
return
for i in range(start, len(candidates)):
if candidates[i] > target:
continue
if i > start and candidates[i] == candidates[i-1]:
continue
tracks.append(candidates[i])
self.backtrack(candidates, tracks, target - candidates[i], i+1)
tracks.pop()
Permutations II (Leetcode 47)
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
self.res = []
tracks = []
used = [False] * len(nums)
nums.sort()
self.backtrack(nums, tracks, used)
return self.res
def backtrack(self, nums, tracks, used):
if len(nums) == len(tracks):
self.res.append(tracks.copy())
for i in range(len(nums)):
if i>0 and nums[i-1] == nums[i] and (not used[i-1]):
continue
if used[i]:
continue
tracks.append(nums[i])
used[i] = True
self.backtrack(nums, tracks, used)
tracks.pop()
used[i] = False
Combination Sum (Leetcode 39)
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
self.res = []
tracks = []
self.backtrack(candidates, tracks, target, 0)
return self.res
def backtrack(self, candidates, tracks, target, start):
if target == 0:
self.res.append(tracks.copy())
for i in range(start, len(candidates)):
if candidates[i] > target:
continue
tracks.append(candidates[i])
self.backtrack(candidates, tracks, target - candidates[i], i)
tracks.pop()
Island
Overview
The 2D array can always be considered as a 4-ary tree, with 4 neighbors as children. The template is as follows:
def dfs(grid: List[List[int]], i: int, j: int, visited: List[List[bool]]) -> None:
m, n = len(grid), len(grid[0])
if i < 0 or j < 0 or i >= m or j >= n:
return
if visited[i][j]:
return
visited[i][j] = True
dfs(grid, i - 1, j, visited) # up
dfs(grid, i + 1, j, visited) # down
dfs(grid, i, j - 1, visited) # left
dfs(grid, i, j + 1, visited) # right
Number of Islands (Leetcode 200)
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, row, col):
if row <0 or row >= len(grid) or col <0 or col >= len(grid[0]):
return
if grid[row][col] == '0':
return
grid[row][col] = '0'
dfs(grid, row, col+1)
dfs(grid, row, col-1)
dfs(grid, row+1, col)
dfs(grid, row-1, col)
res = 0
n_row, n_col = len(grid), len(grid[0])
for row in range(n_row):
for col in range(n_col):
if grid[row][col] == '1':
res += 1
dfs(grid, row, col)
return res
Number of Closed Islands (Leetcode 1254)
class Solution:
def closedIsland(self, grid: List[List[int]]) -> int:
def dfs(grid, row, col):
if row <0 or row >= len(grid) or col <0 or col >= len(grid[0]):
return
if grid[row][col] == 1:
return
grid[row][col] = 1
dfs(grid, row, col+1)
dfs(grid, row, col-1)
dfs(grid, row+1, col)
dfs(grid, row-1, col)
res = 0
n_row, n_col = len(grid), len(grid[0])
for row in range(n_row):
dfs(grid, row, 0)
dfs(grid, row, n_col-1)
for col in range(n_col):
dfs(grid, 0, col)
dfs(grid, n_row-1, col)
for row in range(n_row):
for col in range(n_col):
if grid[row][col] == 0:
res += 1
dfs(grid, row, col)
return res
Max Area of Island (Leetcode 695)
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
def dfs(grid, row, col):
if row <0 or row >= len(grid) or col <0 or col >= len(grid[0]):
return 0
if grid[row][col] == 0:
return 0
grid[row][col] = 0
count = dfs(grid, row, col+1)
count += dfs(grid, row, col-1)
count += dfs(grid, row+1, col)
count += dfs(grid, row-1, col)
return count + 1
max_count = 0
n_row, n_col = len(grid), len(grid[0])
for row in range(n_row):
for col in range(n_col):
if grid[row][col] == 1:
count = dfs(grid, row, col)
if count > max_count:
max_count = count
return max_count
Count Sub Islands (Leetcode 1905)
class Solution:
def countSubIslands(self, grid1: List[List[int]], grid2: List[List[int]]) -> int:
def dfs(grid, row, col):
if row <0 or row >= len(grid) or col <0 or col >= len(grid[0]):
return
if grid[row][col] == 0:
return
grid[row][col] = 0
dfs(grid, row, col+1)
dfs(grid, row, col-1)
dfs(grid, row+1, col)
dfs(grid, row-1, col)
res = 0
n_row, n_col = len(grid1), len(grid1[0])
for row in range(n_row):
for col in range(n_col):
if grid1[row][col] == 0 and grid2[row][col] == 1:
dfs(grid2, row, col)
for row in range(n_row):
for col in range(n_col):
if grid2[row][col] == 1:
res += 1
dfs(grid2, row, col)
return res