回溯
回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解的话(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化来舍弃该解,即“回溯”。
以下是回溯法的一般解题模板:
定义问题的解空间:确定问题的所有可能解的范围,这个范围被称为解空间。
确定解空间的组织结构:解空间通常是一个树或图的结构。对于每个节点,确定其子节点的生成规则。
搜索解空间:从根节点开始,按照生成规则逐层向下搜索,直到找到满足条件的解。
回溯:当搜索到某一层时,发现当前节点不能得到满足条件的解,就回退到上一层,继续搜索。
记录结果:在搜索过程中,将满足条件的解记录下来。
子集
class Solution():
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
result = []
self.backtrack(result, [], nums, 0)
return result
def backtrack(self, result, temp, nums, start):
# 将当前组合添加到结果列表中
result.append(temp[:])
# 从start开始遍历数组
for i in range(start, len(nums)):
# 将当前元素添加到临时列表中
temp.append(nums[i])
# 递归调用回溯函数,继续寻找下一个元素
self.backtrack(result, temp, nums, i + 1)
# 回溯,移除最后一个元素
temp.pop()
组合
class Solution():
def combine(self, n, k):
"""
:type n: int
:type k: int
:rtype: List[List[int]]
"""
result = []
self.backtrack(result, [], n, k, 1)
return result
def backtrack(self, result, temp, n, k, start):
# 如果临时列表的长度等于k,将其添加到结果列表中
if len(temp) == k:
result.append(temp[:])
return
# 从start开始遍历到n
for i in range(start, n + 1):
# 将当前元素添加到临时列表中
temp.append(i)
# 递归调用回溯函数,继续寻找下一个元素
self.backtrack(result, temp, n, k, i + 1)
# 回溯,移除最后一个元素
temp.pop()
排列
'''
排列
'''
class Solution():
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
result = []
self.backtrack(result, [], nums)
return result
def backtrack(self, result, temp, nums):
# 如果临时列表的长度等于nums的长度,将其添加到结果列表中
if len(temp) == len(nums):
result.append(temp[:])
return
# 遍历nums中的每个元素
for i in range(len(nums)):
# 如果该元素已经在临时列表中,跳过
if nums[i] in temp:
continue
# 将当前元素添加到临时列表中
temp.append(nums[i])
# 递归调用回溯函数,继续寻找下一个元素
self.backtrack(result, temp, nums)
# 回溯,移除最后一个元素
temp.pop()
全排列
'''
全排列
'''
class Solution():
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
result = []
self.used= False*len(nums)
nums.sort() # 先对数组进行排序,保证字典序递增
self.backtrack(result, [], nums)
return result
def backtrack(self, result, temp, nums):
# 如果临时列表的长度等于nums的长度,将其添加到结果列表中
if len(temp) == len(nums):
result.append(temp[:])
return
# 遍历nums中的每个元素
for i in range(len(nums)):
# 如果该元素已经在临时列表中,或者与前一个元素相同且前一个元素还未使用过,跳过
if i > 0 and nums[i] == nums[i - 1] and not self.used[i - 1]:
continue
# 如果该元素已经使用过,跳过
if self.used[i]:
continue
# 标记该元素为已使用
self.used[i] = True
# 将当前元素添加到临时列表中
temp.append(nums[i])
# 递归调用回溯函数,继续寻找下一个元素
self.backtrack(result, temp, nums)
# 回溯,移除最后一个元素并标记为未使用
temp.pop()
self.used[i] = False
组合总数
'''
组合总数
'''
class Solution:
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
result = []
candidates.sort() # 先对数组进行排序,方便剪枝操作
self.backtrack(result, [], candidates, target, 0)
return result
def backtrack(self, result, temp, candidates, target, start):
# 如果临时列表的元素之和等于target,将其添加到结果列表中
if sum(temp) == target:
result.append(temp[:])
return
# 如果临时列表的元素之和大于target,直接返回,不再继续搜索
if sum(temp) > target:
return
# 从start开始遍历candidates中的每个元素
for i in range(start, len(candidates)):
# 将当前元素添加到临时列表中
temp.append(candidates[i])
# 递归调用回溯函数,继续寻找下一个元素
self.backtrack(result, temp, candidates, target, i)
# 回溯,移除最后一个元素
temp.pop()
组合总数2
'''
组合总数2
'''
class Solution:
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
result = []
candidates.sort() # 先对数组进行排序,方便剪枝操作和去重操作
self.backtrack(result, [], candidates, target, 0)
return result
def backtrack(self, result, temp, candidates, target, start):
# 如果临时列表的元素之和等于target,将其添加到结果列表中
if sum(temp) == target:
result.append(temp[:])
return
# 如果临时列表的元素之和大于target,直接返回,不再继续搜索
if sum(temp) > target:
return
# 从start开始遍历candidates中的每个元素,注意避免重复组合的出现
for i in range(start, len(candidates)):
# 如果当前元素与前一个元素相同,并且前一个元素还未使用过,跳过本次循环,避免重复组合的出现
if i > start and candidates[i] == candidates[i - 1]:
continue
# 将当前元素添加到临时列表中
temp.append(candidates[i])
# 递归调用回溯函数,继续寻找下一个元素
self.backtrack(result, temp, candidates, target, i + 1)
# 回溯,移除最后一个元素
temp.pop()
解数独
def is_valid(board, row, col, num):
# 检查行是否包含数字
for i in range(9):
if board[row][i] == num:
return False
# 检查列是否包含数字
for i in range(9):
if board[i][col] == num:
return False
# 检查3x3方格是否包含数字
start_row = row - row % 3
start_col = col - col % 3
for i in range(3):
for j in range(3):
if board[i + start_row][j + start_col] == num:
return False
return True
def solve_sudoku(board):
empty_cell = find_empty_cell(board)
if not empty_cell:
return True
row, col = empty_cell
for num in range(1, 10):
#当前位置只能判断和之前填过的有没有冲突,后续填了有冲突会撤销
if is_valid(board, row, col, num):
board[row][col] = num
if solve_sudoku(board):
return True
#回溯
board[row][col] = 0
return False
def find_empty_cell(board):
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == 0:
return (i, j)
return None
# 示例数独
board = [
[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9]
]
if solve_sudoku(board):
for row in board:
print(row)
else:
print("无解")
n皇后
def solveNQueens(n):
def is_valid(board, row, col):
# 检查同一列是否有其他皇后
for i in range(row):
if board[i][col] == 'Q':
return False
# 检查左上对角线是否有其他皇后
i, j = row - 1, col - 1
while i >= 0 and j >= 0:
if board[i][j] == 'Q':
return False
i -= 1
j -= 1
# 检查右上对角线是否有其他皇后
i, j = row - 1, col + 1
while i >= 0 and j < n:
if board[i][j] == 'Q':
return False
i -= 1
j += 1
return True
def backtrack(board, row):
#边界
if row == n:
result.append([''.join(row) for row in board])
return
for col in range(n):
#在row col添加q是否有效
if not is_valid(board, row, col):
continue
board[row][col] = 'Q'
#回溯
backtrack(board, row + 1)
#撤销 彻底结束到n,或者中间有失败的就会for循环结束就会返回上一层,当前状态的后续状态都尝试完了
board[row][col] = '.'
result = []
board = [['.' for _ in range(n)] for _ in range(n)]
backtrack(board, 0)
return result