Day24 算法学习|回溯算法01 组合问题

回溯原理

  • 深度优先搜索
    回溯算法在解决问题时,通常从根节点开始,先选择一条路线(选择一种解决方案)深入到底,也就是说,它优先搜索树或图的深度。

  • 遇到阻碍就回退
    当搜索到某一节点时,如果判断这个节点无法达到目标(满足结束条件),就会放弃对这条路线的深入,回溯到上一层,尝试其他可能的路径。

  • 穷举所有路径
    回溯算法会逐个试遍所有可能的解决方案,直到找出所有满足条件的解,或者搜索完整棵树(所有可能的路径)。

  • 避免重复和剪枝
    为了提高效率,回溯算法在实际操作中,会加入一些剪枝操作,比如某些情况下,如果早点发现这条路径不可能是正确解,就可以提前结束这次搜索,避免无效的遍历。同时,对已经遍历过的状态,一般会进行标记,避免重复遍历。

回溯三部曲

  • 确定参数返回值
  • 确定终止条件
  • 单层循环逻辑

回溯模版

def backtracking(args):
    if '终止条件':
        # 存放结果
        return

    for '选择' in '本层集合中元素':
        # 处理节点
        backtracking('路径', '选择列表')  # 递归
        # 回溯,撤销处理结果

回溯问题分类

  • 组合问题
  • 切割问题
  • 子集问题
  • 排列问题
  • 棋盘问题(还没开始)

题目 77. 组合

问题描述

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

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

解题思路

这里是引用

代码

  • 未剪枝优化
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        result=[]
        self.backtracking(n,k,1,[],result)
        return result

    def backtracking(self,n,k,startIndex,path,result):
        #终止条件
        if len(path)==k:
            result.append(path[:])
            return
        #单层递归逻辑
        for i in range(startIndex, n+1):
            path.append(i)
            self.backtracking(n,k,i+1,path,result)
            path.pop()


递归过程详解

调用层级ipathresult描述
11[1][]从1开始, 将1添加到路径中
22[1, 2][]再从2开始, 将2添加到路径中,路径长度等于2,存入结果中
11[1][[1, 2]]回溯到层级1, 撤销路径中的2
23[1, 3][[1, 2]]从3开始, 将3添加到路径中,路径长度等于2,存入结果中
11[1][[1, 2], [1, 3]]回溯到层级1, 撤销路径中的3
24[1, 4][[1, 2], [1, 3]]从4开始, 将4添加到路径中,路径长度等于2,存入结果中
11[1][[1, 2], [1, 3], [1, 4]]回溯到层级1, 撤销路径中的4
11[][[1, 2], [1, 3], [1, 4]]回溯到层级1, 撤销路径中的1
12[2][[1, 2], [1, 3], [1, 4]]从2开始, 将2添加到路径中
后面的步骤同理,会继续这样递归下去,直到所有的可能性都被遍历完
  • 剪枝优化:
    如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。i就是for循环的起始位置:
    在这里插入图片描述
  1. 已经选择的元素 path.size()
  2. 还需要选择的元素,k-path.size()
  3. n-(k-path.size)为剩下可以选的元素,应该要大于0,否则不够就要剪枝
  4. 举个例子,每次循环i都有个startindex,像需要4个元素的和为某个值,但是组合长度为5,startindex等于2,后面的元素都凑不齐4个。当startindex等于1,即第二个元素的时候,刚好凑齐4个元素,所以我们要找到刚好能凑齐目标个数的最大startindex。
  5. 这个最大startindex就等于,n-(还需要选择的个数)
  6. 此题注意,因为是append(i),所以i只能从1开始选取,range的最大值也要相应加1,n-(k-len(path))+2

这里是引用在这里插入图片描述

  • 可以看到在第一个循环中i只能取1,因为从2开始都凑不成4个,所以从2开始不能取, 第一个循环为for i=(0,4-(4-0)+1),(0,1)
  • 第二层已经选了1个(len(path)),所以还要选3个(k-len(path)),最大能从2开始选, index: n-(k-len(path)),但是 是 range,所以接下来要加1。

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

    def backtracking(self,n,k,path,res,startIndex):
        #终止条件
        if len(path)==k:
            res.append(path[:])    
            return    
        #单层递归逻辑
        for i in range(startIndex, n-(k-len(path))+2):
            path.append(i)
        #backtracking
            self.backtracking(n,k,path,res,i+1)
            path.pop()

复杂度分析

  • 时间复杂度O(C(n, k))

    生成组合的问题,从n个数里面选取k个

  • 空间复杂度O(C(n, k) * k)

    每个组合有k个数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值