回溯算法-Python
回溯算法
回溯算法是一种系统的搜索算法,用于解决诸如排列组合、子集生成、图的路径、棋盘问题等问题。其核心思想是通过递归尝试各种可能的解决方案,遇到不满足条件的解时则回退(回溯),继续尝试其他可能性,直到找到所有的解决方案或确认无解。
主要步骤:
- 选择路径: 在当前步骤选择一个可能的路径。
- 递归探索: 对当前选择的路径进行递归探索,继续尝试后续步骤。
- 约束检查: 检查当前选择是否满足问题的约束条件。如果不满足,则回溯。
- 回溯; 如果当前路径无法导致有效解,则回退到上一步,尝试其他可能的路径。
# 回溯算法框架
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
1.没有重复数字的全排列
leetcode类似问题: 46. 全排列
给出一组数字,返回该组数字的所有排列
例如:[1,2,3]的所有排列如下
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]
class Solution:
def __init__(self):
self.res = []
def backtrack(self, nums, path, used):
# 满足结束条件
if len(path) == len(nums):
self.res.append(path.copy())
return
for i in range(len(nums)):
# 剔除已选择的元素
if used[i]:
continue
# 做选择
path.append(nums[i])
used[i] = True
self.backtrack(nums, path, used)
# 撤销选择
path.pop()
used[i] = False
def permute(self, nums):
# 记录路径
path = []
# 标记选择是否被使用
used = [False] * len(nums)
self.backtrack(nums, path, used)
return self.res
另一种写法
class Solution:
def permute(self, nums):
result = []
def backtrack(start):
# 当 start 等于 nums 的长度时,表示当前排列完成
if start == len(nums):
# 将当前排列加入结果列表
result.append(nums[:])
return
for i in range(start, len(nums)):
# 交换当前元素与 start 位置的元素
nums[start], nums[i] = nums[i], nums[start]
# 递归生成排列
backtrack(start + 1)
# 回溯,恢复数组的状态
nums[start], nums[i] = nums[i], nums[start]
backtrack(0)
return result
2.生成所有子集
leetcode类似问题: 78. 子集
给定一个集合 [1, 2, 3],我们需要生成这个集合的所有可能的子集。
class Solution:
def subsets(self, nums):
res = []
path = []
def backtrack(nums, start, res, path):
# 将当前路径加入结果中
res.append(path.copy())
# 从start位置开始,尝试添加更多的元素到路径中
for i in range(start, len(nums)):
# 做选择
path.append(nums[i])
# 递归调用
backtrack(nums, i + 1, res, path)
# 撤销选择(回溯)
path.pop()
backtrack(nums, 0, res, path)
return res
3.数独问题
leetcode类似问题: 37. 解数独
根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。
class Solution:
def is_valid(self, board, row, col, num):
for i in range(9):
# 检查行
if board[row][i] == num:
return False
# 检查列
if board[i][col] == num:
return False
# 检查3x3子网格
start_row = (row // 3) * 3
start_col = (col // 3) * 3
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == num:
return False
return True
def solveSudoku(self, board):
for i in range(9):
for j in range(9):
if board[i][j] == ".":
# 尝试填入数字1-9
for n in "123456789":
if self.is_valid(board, i, j, n):
board[i][j] = n
# 递归调用,尝试解决下一个空位置
if self.solveSudoku(board):
return True
# 如果递归调用返回 False,撤销当前填入的 n
board[i][j] = "."
# 如果所有数字都尝试过还不能解决,返回 False,触发回溯
return False
return True
4.八皇后问题
leetcode类似问题: 51. N 皇后
在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
class Solution:
def is_valid(self, board, row, col):
# 检查列是否有皇后
for i in range(row):
if board[i][col] == 'Q':
return False
# 检查左上对角线是否有皇后
for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
if board[i][j] == 'Q':
return False
# 检查右上对角线是否有皇后
for i, j in zip(range(row, -1, -1), range(col, len(board), 1)):
if board[i][j] == 'Q':
return False
return True
def backtrack(self, board, row, res):
# 如果所有行都放置了皇后,表示找到一个解决方案
if row == len(board):
res.append(board.copy())
return
for col in range(len(board)):
if self.is_valid(board, row, col):
# 做选择
board[row] = board[row][:col] + 'Q' + board[row][col + 1:]
# 进入下一行决策
self.backtrack(board, row + 1, res)
# 撤销选择
board[row] = board[row][:col] + '.' + board[row][col + 1:]
def solveNQueens(self, n):
res = []
# 初始化棋盘
board = ["." * n for _ in range(n)]
self.backtrack(board, 0, res)
return res
参考资料