Leetcode刷题笔记25:回溯2(组合总和III-电话号码组和)

136 阅读3分钟

导语

leetcode刷题笔记记录,本篇博客记录回溯部分的题目,主要题目包括:

  • 216 组合总和III
  • 17 电话号码的字母组合

216 组合总和III

题目描述

找出所有相加之和为 n **的 k ****个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次 

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60

解法

这里还是引用几张代码随想录中的插图,

image.png

相比于上一篇博客中的那道题,这道题要求只取符合某个特定条件的叶子节点,所以在单层递归中要修改递归结束条件,这里为:

        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次剪枝:

  1. 当sum已经大于n时,可以直接返回(因为都是正数,再加下去肯定超了);
  2. for循环的i的上界可以缩小,具体示意图如下(图片来自:代码随想录):

image.png

剪枝后的完整代码为:

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 的新副本,而不是添加其引用。这样,回溯修改不会影响已保存的结果。

image.png

Leetcode 17 电话号码的字母组合

题目描述

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入: digits = "23"
输出: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

提示:

  • 0 <= digits.length <= 4
  • digits[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 的新副本,而不是添加其引用。这样,回溯修改不会影响已保存的结果。