导语
leetcode刷题笔记记录,本篇博客记录回溯部分的题目,主要题目包括:
- 216 组合总和III
- 17 电话号码的字母组合
216 组合总和III
题目描述
找出所有相加之和为 n **的 k ****个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
提示:
2 <= k <= 91 <= n <= 60
解法
这里还是引用几张代码随想录中的插图,
相比于上一篇博客中的那道题,这道题要求只取符合某个特定条件的叶子节点,所以在单层递归中要修改递归结束条件,这里为:
if len(self.path) == k:
if sum == n:
self.result.append(self.path[:])
return
完整的回溯代码如下:
class Solution:
def __init__(self):
self.result = []
self.path = []
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
self.back_tracking(k, n, 0, 1)
return self.result
def back_tracking(self, k, n, sum, start_index):
if len(self.path) == k:
if sum == n:
self.result.append(self.path[:])
return
for i in range(start_index, 10):
sum += i
self.path.append(i)
self.back_tracking(k, n, sum, i+1)
self.path.pop()
sum -= i
其中,一共可以进行2次剪枝:
- 当sum已经大于n时,可以直接返回(因为都是正数,再加下去肯定超了);
- for循环的i的上界可以缩小,具体示意图如下(图片来自:代码随想录):
剪枝后的完整代码为:
class Solution:
def __init__(self):
self.result = []
self.path = []
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
self.back_tracking(k, n, 0, 1)
return self.result
def back_tracking(self, k, n, sum, start_index):
if sum > n:
return
if len(self.path) == k:
if sum == n:
self.result.append(self.path[:])
return
for i in range(start_index, 9-(k-len(self.path))+2):
sum += i
self.path.append(i)
self.back_tracking(k, n, sum, i+1)
self.path.pop()
sum -= i
易错点
在终止条件内往result添加结果时,如果使用
self.result.append(self.path)
当直接向 self.result 中添加 self.path 时,实际上是在添加一个指向 self.path 的引用。这意味着,后续对 self.path 的任何修改(例如使用 pop() 方法)都会影响到之前添加到 self.result 的内容。因此,当代码执行完毕后,所有添加到 self.result 中的列表都是空的,因为在回溯过程中已经清空了 self.path。
为了修复这个问题,需要将 self.path 的当前状态(或其副本)添加到 self.result 中,而不是添加一个指向它的引用。可以使用 list() 或 self.path[:] 来实现这一点:
修改这一行:
self.result.append(self.path)
为:
self.result.append(self.path[:])
这样,每次迭代时,都会向 self.result 添加 self.path 的新副本,而不是添加其引用。这样,回溯修改不会影响已保存的结果。
Leetcode 17 电话号码的字母组合
题目描述
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入: digits = "23"
输出: ["ad","ae","af","bd","be","bf","cd","ce","cf"]
提示:
0 <= digits.length <= 4digits[i]是范围['2', '9']的一个数字。
解法
还是套用之前的模板,这里时刻牢记,搜索的广度是for循环实现,而深度则是由回溯把控。这里是返回所有可能的组合,因此返回条件只要判断回溯的深度即可。
具体代码如下:
class Solution:
def __init__(self):
self.s = ""
self.result = []
self.num2letter = {
"2": "abc",
"3": "def",
"4": "ghi",
"5": "jkl",
"6": "mno",
"7": "pqrs",
"8": "tuv",
"9": "wxyz"
}
def letterCombinations(self, digits: str) -> List[str]:
if not digits:
return []
self.back_tracking(digits, 0)
return self.result
def back_tracking(self, digits, index):
# index 用于表示当前处理到了哪一个数字
if index == len(digits):
self.result.append(self.s[:])
return
letters = self.num2letter[digits[index]]
for letter in letters:
self.s += letter
self.back_tracking(digits, index+1)
self.s = self.s[:-1]
易错点
self.result.append(self.path)
当直接向 self.result 中添加 self.path 时,实际上是在添加一个指向 self.path 的引用。这意味着,后续对 self.path 的任何修改(例如使用 pop() 方法)都会影响到之前添加到 self.result 的内容。因此,当代码执行完毕后,所有添加到 self.result 中的列表都是空的,因为在回溯过程中已经清空了 self.path。
为了修复这个问题,需要将 self.path 的当前状态(或其副本)添加到 self.result 中,而不是添加一个指向它的引用。可以使用 list() 或 self.path[:] 来实现这一点:
修改这一行:
self.result.append(self.path)
为:
self.result.append(self.path[:])
这样,每次迭代时,都会向 self.result 添加 self.path 的新副本,而不是添加其引用。这样,回溯修改不会影响已保存的结果。