刷题记录 回溯算法-2,3:77. 组合

发布于:2025-02-10 ⋅ 阅读:(35) ⋅ 点赞:(0)

题目:77. 组合

难度:中等

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

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

一、构造回溯

1.填充模板

0.模板

回溯树的最大宽度:n,每一轮可能的数字的个数

回溯树的最大深度:k,组合长度

 模板:

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

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

步骤:1.函数(无返回值,参数多) + 2.if(终止){收集+return} + 3.for(搜索集){处理;递归;回溯}

1.函数(无返回值,参数多)

参数:

backtracking(self, n, k, start_idx, path, ans)

题目条件:n, k

起始位置:start_idx,用于标记哪些数字已经被访问

结果数组:ans

记录当前组合的数组:path

2.if(终止){收集+return}

终止条件(回溯深度):长度:len(组合数组) == k

if len(path) == k:

收集结果:组合数组插入结果数组中

ans.append(path[:])

不要忘记return,不然会报错超过递归上限

3.for(搜索集){处理;递归;回溯}

搜索集合(回溯宽度):起始位置start_idx到n + 1,

其物理意义是所有未访问的数字

for i in range(start_idx, n + 1):

处理:组合数组插入元素

path.append(i)

递归:起始位置:当前插入的数字 + 1,表示当前插入数字已被访问

self.backtracking(n, k, i + 1, path, ans)

回溯:组合数组末尾元素抛出,撤回到上一层的结果

path.pop()

2.代码实现

填充模板:

def backtracking(self, n, k, start_idx, path, ans):
        if len(path) == k:
            ans.append(path[:])
            return
        
        for i in range(start_idx, n + 1):
            path.append(i)
            self.backtracking(n, k, i + 1, path, ans)
            path.pop()

完整代码(python): 

class Solution:
    def backtracking(self, n, k, start_idx, path, ans):
        if len(path) == k:
            ans.append(path[:])
            return
        
        for i in range(start_idx, n + 1):
            path.append(i)
            self.backtracking(n, k, i + 1, path, ans)
            path.pop()

    def combine(self, n: int, k: int) -> List[List[int]]:
        ans = []
        self.backtracking(n, k, 1, [], ans)
        return ans

二、剪枝优化

1.思路

完全的暴力搜索常常会访问很多废路径,

确切的说就是长度len(path)达不到长度k的路径

组合问题是不能重复访问同一个数字的,

如果某一轮访问了太多的数字,

就会导致路径的后续没有足够的数字使组合数组长度达到k,

这条路径也就作废了,或者说本就不存在这条路径

那如何保证每一条路径都是有效的呢?

解决的方法就是控制每个节点搜索集末端位置

(在n序列中走的最远的路径)

整个搜索集的大小是n

当一条路径到了某个节点,

已经访问的数字的个数是len(path),

那么还需要访问的次数为(k - len(path)),

所以单个节点搜索集的末端

(回溯树节点内横向按顺序访问,

且每个节点的前端位置不同)

也就是能留给路径上后续节点访问的数字个数最多就是n - (k - len(path)),

也就是某个节点搜索集的末端的长度一旦超过n - (k - len(path)),

该路径的后续步骤就没有足够的节点可以访问了,

所以需要把回溯树的宽度从n调整到n - (k - len(path)) + 1,

末尾的+1是本轮访问的节点

2.实现

完整代码(python):

class Solution:
    def backtracking(self, n, k, start_idx, path, ans):
        if len(path) == k:
            ans.append(path[:])
            return
        for i in range(start_idx, n + 1 - (k - len(path) - 1)):
            path.append(i)
            self.backtracking(n, k, i + 1, path, ans)
            path.pop()

    def combine(self, n: int, k: int) -> List[List[int]]:
        ans = []
        self.backtracking(n, k, 1, [], ans)
        return ans

 

 

 

 


网站公告

今日签到

点亮在社区的每一天
去签到