回溯法第一天,77. 组合,216. 组合总和 III,17. 电话号码的字母组合。由于之前二叉树做过挺多次递归和回溯,感觉对基本做法和思想还是比较熟悉的。但是在这部分中,递归和回溯的思考更加抽象了,感觉像是在二叉树环节中,能够看着图去思考会轻松一点,但是现在这些题都需要自己去想象为一个树形,所以会更加考验抽象思考能力一些。
先贴一个回溯模板
def backtracking(path, choices):
if 终止条件:
保存结果 path.copy()
return
for choice in choices: # 当前层的所有选择
做选择(添加到 path)
backtracking(path, 剩余选择) # 递归
撤销选择(回溯)
216. 组合总和 III
回溯法经典组合类型题目,与 77. 组合 类似,找出1-9中,k个能够加和为n的组合。继续使用递归三步分析。
首先是参数,参数不需要在一开始写的时候就列全,后面想到了再补也行。这道题主要是k, n, index/start(为下一层for循环搜索的起始位置), path(单个数组), res(二维数组,答案)。
然后去找终止条件,如果大于等于的话就是不需要添加开始回溯,但是其中特列是如果等于,就将目前的数组存入res。这里有一个重点,是需要存入浅拷贝的path,因为如果不这么做,加入res的path也会跟着后续回溯发生变化。
最后是单层内操作,也就是for循环内的操作,首先添加当前元素到path数组,然后开始递归,如果不能元素重复的话,记得下一次递归是要i+1,最后pop弹出最外层,也就是真正回溯的操作。然后这里有一个剪枝的操作,意思就是:你最多还需要 (k - len(path))
个数(k为需要选取的个数),而当前数字范围是 1~9,如果从i 开始后面的数都不够用了,可以直接跳出。于是就是9-(k-len(path)) + 1,后面一个+1是range的边界问题。其实还有一种反向遍历写法会看起来简洁一点,但是后边range和递归方向会变,脑袋笨掌握好一种正序就行了(其实估计自己碰到新题的时候连剪枝都想不起来,能不超时ac就已经万幸了)。
其实对这种简单结构的递归掌握还行,上点难度判断条件多一点的递归脑子就一团浆糊了。
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
res = []
self.backtracking(k,n,1,[],res)
return res
def backtracking(self, k, n, start, path, res):
if len(path) == k:
if sum(path) == n:
res.append(path.copy())
return
for c in range(start, 10): # 剪枝range(start, 9 - (k - len(path))+1+1)
path.append(i)
self.backtracking(k,n, i + 1, path, res)
path.pop()
17. 电话号码的字母组合
非常有意思的一道题,一开始还想不到怎么把文字输入进去,感觉用字典会很麻烦,偷看一眼题解发现还有这种形式,学到了,用列表的索引值去对应相应的数字。然后再尝试去做,做了个大概,有些细节处理还是不到位。还是按三步走分析一下
需要的参数感觉还是差不多,还是当前数组path以及答案的二维数组result,以及index。
然后是终止条件,就是长度到达len(digits),记住用字符串的操作''.join(path)。
最后是单层递归逻辑,就是完全一样的添加,迭代,回溯三步。
class Solution:
def __init__(self):
self.mapping = ['','','abc','def','ghi','jkl','mno','pqrs','tuv','wxyz']
def backtracking(self, digits, index, path, result):
n = len(digits)
if index == n:
result.append(''.join(path))
return
letters = self.mapping[int(digits[index])]
for c in letters:
path.append(c)
self.backtracking(digits, index + 1, path, result)
path.pop()
def letterCombinations(self, digits: str) -> List[str]:
res = []
if not digits:
return []
self.backtracking(digits, 0, [], res)
return res
还需多练习递归的思想,感觉有时候递归+回溯确实脑袋里很难有清楚的思路,即使明白三步但也很容易卡住。相较于二叉树中的递归,这部分题目感觉更加抽象了起来。