回溯算法
1. 原理:通过递归地构建解决方案,并在发现当前解不符合条件时,“回溯”到上一步,尝试其他可能的路径。
2. 应用:
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
3. 经典结构
def backtrack(路径, 选择列表):
if 满足结束条件:
result.append(路径)
return
for 选择 in 选择列表:
# 做选择
路径.append(选择)
# 递归进入下一层
backtrack(路径, 新的选择列表)
# 撤销选择(回溯)
路径.pop()
4. 优缺点:纯暴力,不高效
• 优点:可以求解所有解的组合,保证找到所有可能解。
• 缺点:在最坏情况下,回溯算法的时间复杂度会非常高,接近于指数级别,适合使用剪枝策略优化。
77. 组合
解题思路:组合是无序的,需剔除重复值
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
result = []
def backtrack(start: int, path: List[int]):
# 如果组合的长度达到了 k,收集当前组合
if len(path) == k:
result.append(list(path)) # 这里复制一份 path,并加入结果列表
return
# 从当前数字 start 开始,尝试添加数字到组合中
for i in range(start, n + 1):
# 做选择:将当前数字 i 添加到 path 中
path.append(i)
# 递归调用,构建后续组合
backtrack(i + 1, path)
# 回溯:撤销选择
path.pop()
# 从 1 开始构建组合
backtrack(1, [])
return result
剪枝优化:for loop里限制为 range(start, n - (k - len(path)) + 2)
1. 还需要选 k - len(path) 个元素才能达到组合的长度 k
2. 为了保证剩下的数字够用,最大位置应为 n - (k - len(path)) + 1
3. range(start, end) 是左闭右开的区间,右边界设为 n - (k - len(path)) + 2,这样才能确保包含位置 n - (k - len(path)) + 1
216. 组合总和 III
示例 2:
输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]] 解释: 1 + 2 + 6 = 9 1 + 3 + 5 = 9 2 + 3 + 4 = 9
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
result= []
def backtracking (currentSum, startIndex, path):
if currentSum > n:
return
if len(path) == k:
if currentSum == n:
result.append(list(path))
for i in range(startIndex, 9-(k-len(path))+2):
currentSum += i
path.append(i)
backtracking(currentSum, i+1, path)
currentSum -= i
path.pop()
backtracking(0, 1, [])
return result
17. 电话号码的字母组合
class Solution:
def __init__(self):
# 数字到字母的映射表,索引对应数字的值
self.letterMap = [
"", # 0 - 无映射
"", # 1 - 无映射
"abc", # 2 - 对应字母 abc
"def", # 3 - 对应字母 def
"ghi", # 4 - 对应字母 ghi
"jkl", # 5 - 对应字母 jkl
"mno", # 6 - 对应字母 mno
"pqrs", # 7 - 对应字母 pqrs
"tuv", # 8 - 对应字母 tuv
"wxyz" # 9 - 对应字母 wxyz
]
# 存储结果的列表
self.result = []
def getCombinations(self, digits, index, s):
# 递归终止条件:当 index 到达 digits 的长度,说明组合已完成
if index == len(digits):
self.result.append(s) # 将当前组合 s 加入结果列表
return
# 当前数字对应的字符
digit = int(digits[index]) # 将当前字符转换为数字
letters = self.letterMap[digit] # 从映射表中获取数字对应的字母字符串
# 遍历当前数字对应的所有字母
for letter in letters:
# 递归处理下一个数字,将当前字母加入组合
self.getCombinations(digits, index + 1, s + letter)
def letterCombinations(self, digits: str) -> List[str]:
# 特殊情况处理:如果输入为空字符串,直接返回空结果
if len(digits) == 0:
return self.result
# 开始递归,从第 0 个数字开始,初始组合字符串为空
self.getCombinations(digits, 0, '')
return self.result