Leetcode刷题笔记24:回溯1(组合问题求解)

136 阅读2分钟

导语

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

  • 77 组合

知识点

回溯法(Backtracking) 是一种用于查找所有(或部分)解决方案的算法,它解决决策问题通过逐步选择。对于每个决策,回溯法都会尝试所有可能的选项。如果某选项被确定为不可行(即违反了解决方案的条件或约束),则此决策将被撤销,回溯到上一个决策点进行新的选择。这个过程会持续到找到一个有效的解决方案或尝试了所有可能的选项都没有找到解决方案为止。

特点:

  1. 采用试错的思想,尝试每一种可能。
  2. 当当前选项不满足条件或达到问题的边界时,就返回到上一个决策点,进行新的选择。
  3. 通常结合递归算法来实现。

这种策略实际上是一种”暴力“解法,适用于约束满足问题,如八皇后问题、数独、图的着色问题等。根据代码随想录,回溯的模板一般如下:

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

Leetcode 77 组合

题目描述

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入: n = 1, k = 1
输出: [[1]]

 

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

解法

回溯法一定会用到递归,所以这里也会有回溯三部曲:

  1. 递归的参数和返回值
  2. 递归的终止条件
  3. 单层递归的逻辑

下面逐个进行分析: 对于参数和返回值,为了避免过多的参数传递,这里定义全局变量来保存结果,分别定义:

  • path:一次搜索的路径
  • result:二维数组,保存所有符合要求的路径

终止条件则是路径长度满足题目要求的长度;单层逻辑中,需要指定一个开始的index,用于避免重复组合。

初始的回溯解法如下:

class Solution:
    def __init__(self):
        self.result = []  # 用于保存所有可能的结果集
        self.path = []    # 用于保存某一次的可能的路径
    def combine(self, n: int, k: int) -> List[List[int]]:
        self.back_tracking(n, k, 1)

        return self.result

    def back_tracking(self, n, k, start_index):
        if len(self.path) == k:
            self.result.append(self.path[:])  # 这里不能直接append self.path,而是应该path[:]
            return
        for i in range(start_index, n+1):
            self.path.append(i)
            self.back_tracking(n, k, i+1)
            self.path.pop()
        return

但实际上,这个过程中可以进行剪枝,示意图参考代码随想录

image.png

通过约束for循环中i的取值范围,可以提前将一些已经凑不够元素个数k的分支剪掉。

class Solution:
    def __init__(self):
        self.result = []  # 用于保存所有可能的结果集
        self.path = []    # 用于保存某一次的可能的路径
    def combine(self, n: int, k: int) -> List[List[int]]:
        self.back_tracking(n, k, 1)

        return self.result

    def back_tracking(self, n, k, start_index):
        if len(self.path) == k:
            self.result.append(self.path[:])  # 这里不能直接append self.path,而是应该path[:]
            return
        for i in range(start_index, n-k+len(self.path)+2):
            self.path.append(i)
            self.back_tracking(n, k, i+1)
            self.path.pop()
        return

参考

  1. 代码随想录, programmercarl.com/0077.%E7%BB…