回溯算法实践 leetcode第216题

前言

前面讲了回溯算法该怎么写,那篇博文主要目的是为了讲解理论,为了更清楚的呈现该怎么写 我举了个具体例子,在例子中我结合着我提出的理论框架来让大家了解这个写的过程。这次我偏向于实践,再来解决一道回溯问题,我会讲的更详细,带大家熟悉这个解题的过程。下面讲解的内容是以我上篇博文里有些理论为基础的,推荐看我的博文https://blog.csdn.net/Verhan_Cao/article/details/134274778?spm=1001.2014.3001.5502,再来食用。好,我们开始(以下讲解是基于python语言,同样的我会给出详细的备注)

论述

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

只使用数字1到9 每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

https://leetcode.cn/problems/combination-sum-iii/description/

大家先去做一做。尝试后再回来,看我是怎么解决的。

面对一个未知问题,我们一开始并不是直接就写我们的回溯算法了。而是先审题,审好题至少能让你后面的努力是有效的。

我已经审完了,很容易就能想到我通过枚举是可以举出所有的组合的,如果这个组合个数为k而且里面元素和为n,不就完了吗?

我们确定,这是一道回溯算法题。我们的回溯算法的记忆疯狂的涌现出来,我们开始解题了。

1.先写出封装说明

我这样写:函数说明:传入result, lst

因为我大致想了想,我至少得有一个result列表,保存所有的结果这肯定是必须的,要不然我们怎么把所有目标组合返回出去?然后我又想,每一次的组合我又得找一个列表去存它,而且它是动态变化的。(大家如果没有这样想,那就按自己的想法看,能不能行得通,这样做好不好)。

干了什么事呢?要达到什么目的?我们看看题目的要求——“返回所有可能的有效组合的列表”。咦,我们的目的不就是它吗,我们的函数就是要在过程中达到这个要求。好我们的目的就是:把所有的目标组合组合存储到result列表(注意有时候我们的函数功能并不是达到要求)。但这样写没什么用,提供我们不了什么价值,因为它仍然很含糊。组合存哪里?什么又是目标组合,它是通过什么来明确的?

经过思考我给出了这样一个功能描述:将刚传进来lst内元素为基础的所有合题元素打印到result。我结合了组合存放的容器lst与树状图的回溯的一个过程,大家也品一下,是不是这样。注意一下的是递归函数的功能描述的做了什么事,可能有很多事,但是真正描述的应该是关键的有关解决问题的事

所有现在我们的封装说明变为了:传入sum、ls————将刚传进来lst内元素为基础的所有合题元素打印到result————>返回

至于返回什么呢?什么都不需要返回,因为操作的痕迹,已经通过地址给保存了起来。这里要说一下,返回什么或者输入什么可能也仍与你选择的语言和解决的细节策略有关,比如你传入的是指针变量p指向count变量,用来计数,这时候代码逻辑可能有些不一样,但大体的思路是相同的。

现在我们写出函数说明:传入sum、lst————将刚传进来lst内元素为基础的所有合题元素打印到result————>返回void

我要提醒一点,我所说的步骤是主要致力于的工作,并不是说你这个工作中就不需要管其他任何事情了,你做出这个函数说明可能是基于某一些运行逻辑的。

2.确定树的深度和宽度实现的逻辑

深度很好确定,不就是当前组合里k个元素了就去return吗?我们深入了k层,收集了k个元素,收集好了还去判断一下是否累加和为目标值,是目标值就收割这种情况的组合。我们得要个变量来记录当前lst里的个数,我们在函数说明上添加count。函数变成为:传入sum、lst、count————将刚传进来lst内元素为基础的所有合题元素打印到result————>返回void

那宽度呢?宽度又体现在哪呢?

6606e3bef29f447fb5a8064ad0ee07ac.png

看模板蓝色框框里的,宽度是不是就体现在这啊,这一层的枚举的元素,添加进入后就进入深一层的递归,是以这个枚举元素为根节点的小树。能够领悟吗?请大家先前没理解的再思考思考。横向遍历哪些元素,右边界我们是知道的1-9中的9,而左边界是哪?我们发现左边界是动态的,是当前添加元素+1(可以画图更容易理解)。我们的宽度就是 前一个添加的元素 ~ 9。我们得还要一个变量来存储遍历的起始位置。我把它写入到我们的函数说明:传入sum、lst、count(目前元素个数)、dec(启示尝试的数)————将刚传进来lst内元素为基础的所有合题元素打印到result————>void

确定了树的高度和宽度,我们就开始实现代码了

3.结合具体情况用代码实现模板里的逻辑

以下就是我实现的代码,应该经过我们前面的些步骤就容易理解了。

class Solution:
    def combinationSum3(self, k: int, n: int):
        def back_stracking(result, sum, lst, count, dec):
            '''
            函数功能:传入result、sum、lst、count(目前元素个数)、dec(起始尝试的数)————将刚传进来lst内元素为基础的所有合题元素打印到result
            ————>void
            '''
            if count == k: # 判断收集个数是否足够
                if sum == n: # 到则判断sum值是否为n:
                    result.append(lst.copy()) # 如果是,则将此lst,copy一份保存到result
                    return

            for x in range(dec, 10): # 遍历从dec开始的接下来的元素
                lst.append(x)  # 把尝试的数加到lst
                sum += x  # sum进行累加

                back_stracking(result, sum, lst, count + 1, x + 1) # 将目前lst内元素为基础的所有合题元素打印到result

                lst.pop() # 弹出尝试的数
                sum -= x # 取消dec累加的效果

        result = [] # 用来存储最后合题的结果
        sum = 0 # 帮助记录元素的累加值
        lst = [] # 存储这种组合的辅助列表
        back_stracking(result, sum, lst, 0, 1)
        return result

在写的时候我发现,我得要个变量去积累目前lst元素之和的值,所以我又修改了我的函数说明,这没事,我们容易考虑不周到,后面想起来添加进去就行,所以我们的解题的过程不要那么死,可以更加灵活,我们解题的过程中就时时的修改了函数说明。以上就是全部代码了。

总结

仅针对于回溯算法可以直接用上面的解题模式:

1.先写出封装说明 2.确定树的深度和宽度实现的逻辑 3.结合具体情况用代码实现模板里的逻辑

另外还有要提醒大家的就是对于封装说明可能一开始把握不好,后面可以慢慢的去完善、去添加。

~~~~~~

以上就是我本篇想讲的所有内容了,如果这篇文章对你有价值的话,还请点个赞,你的支持对我非常重要!

我是阿航,一位胆大包天、梦想成为大牛的学生~ 

我们下篇文章再聊

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值