第 3 章 常用算法
3.5 回溯算法
3.5.1 概述
回溯算法是一种通过探索所有可能的解来解决问题的算法。回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现,现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用递归方法来实现。
回溯算法的基本步骤可以总结为:
-
选择:在每个决策点选择一个候选解。
-
探索:递归地继续在下一个决策点上做选择。
-
验证:在每次选择之后,检查当前路径是否满足条件。
-
回溯:当某条路径不满足条件或无法继续时,回到上一步,尝试其他可能的选择。
3.5.2 案例一:全排列
力扣46题https://leetcode.cn/problems/permutations/description/
现有一个不含重复数字的数组nums ,返回其所有可能的全排列。
示例:
-
输入:nums = 1,2,3
-
输出:\[1,2,3,1,3,2,2,1,3,2,3,1,3,1,2,3,2,1]
1)思路分析
实现步骤:
-
选择:从待排列的元素中选择一个元素作为当前位置的元素。
-
递归:将该元素固定在当前位置后,递归排列下一个元素。
-
终止条件:所有位置的元素都已确定,添加到结果中并终止递归。
-
回溯:回到上一步,撤销当前的选择,尝试下一个可能的元素。

Python
def permute(nums):
result = []
def backtrack(start):
# 到达末尾,将此时排列结果添加到最终结果中
if start == len(nums):
result.append(nums[:])
return
for i in range(start, len(nums)):
# 选取当前位置的元素:将要选取的元素与此位置的元素互换
if start != i:
nums[start], nums[i] = nums[i], nums[start]
# 递归处理下一个位置的元素
backtrack(start + 1)
# 回溯,恢复原始数组
if start != i:
nums[start], nums[i] = nums[i], nums[start]
backtrack(0)
return result
3.5.3 案例二:N皇后
力扣51题https://leetcode.cn/problems/n-queens/description/
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n皇后问题研究的是如何将n个皇后放置在n×n的棋盘上,并且使皇后彼此之间不能相互攻击。给一个整数n,返回所有的解决方案。每一个方案中 'Q' 和 '.' 分别代表了皇后和空位。

-
输入:n = 4
-
输出:\[".Q..","...Q","Q...","..Q.","..Q.","Q...","...Q",".Q.."]
解释:如上图所示,4皇后问题存在两个不同的解法。
1)思路分析
实现步骤:
-
选择:从棋盘每行中选择一个列放置棋子。
-
验证合法性:每次放置一个皇后时,检查该位置是否与其他已经放置的皇后在列或对角线上发生冲突。
-
递归:棋子放置位置合法则递归放置下一个棋子。
-
终止条件:若所有棋子都放置完则终止递归。
-
回溯:回到上一步,尝试下一个位置。
Python
def n_queens(n):
result = []
cols = set() # 记录哪些列有皇后
diag1 = set() # 记录哪些主对角线上有皇后
diag2 = set() # 记录哪些副对角线上有皇后
# 初始化棋盘
board = [["." for _ in range(n)] for _ in range(n)]
def backtrack(row):
# 如果已经放置了n个皇后,说明找到一个解
if row == n:
result.append(["".join(row) for row in board])
return
for col in range(n):
# 检查当前列和对角线是否有皇后
if col in cols or (row - col) in diag1 or (row + col) in diag2:
continue # 如果有冲突,跳过当前列
# 放置皇后
board[row][col] = "Q"
# 标记当前列和对角线
cols.add(col)
diag1.add(row - col)
diag2.add(row + col)
# 递归处理下一行
backtrack(row + 1)
# 回溯,删除当前位置的皇后,并清理列和对角线的标记
board[row][col] = "."
cols.remove(col)
diag1.remove(row - col)
diag2.remove(row + col)
backtrack(0)
return result