代码随想录算法训练营Day24

力扣22.括号生成【medium】
力扣39.组合总和【medium】
力扣40.组数总和Ⅱ【medium】

一、力扣22.括号生成【medium】

题目链接:力扣22.括号生成
在这里插入图片描述

视频链接:灵茶山艾府

1、思路

  • 可以理解成2n个位置选n个位置放置左括号

  • 从输入的视角:枚举当前位置填左括号还是右括号?本质上是「选或不选」,把填左括号视作「选」,填右括号视作「不选」。(也可以反过来)。

  • 时间复杂度: O ( n ∗ C m n ) O(n*C_m^n) O(nCmn)

    • 分析回溯问题的时间复杂度,有一个通用公式:路径长度×搜索树的叶子数。但由于左右括号的约束,实际上没有这么多叶子,根据 Catalan 数,只有 C m n n + 1 \frac{C_m^n}{n+1} n+1Cmn 个叶子节点,所以实际的时间复杂度为 O(C_m^n)。此外,根据阶乘的 Stirling 公式,时间复杂度也可以表示为 O ( 4 n n ) O( \frac{4^n}{\sqrt{n}}) O(n 4n)

2、代码

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        m = n * 2
        ans =[]
        path = [''] * m
        def dfs(i, open):
            if i == m:
                ans.append(''.join(path))
                return
            if open < n:
                path[i] = '('
                dfs(i+1, open + 1)
            if i - open < open :
                path[i] = ')'
                dfs(i+1, open)
        dfs(0,0)
        return ans

二、力扣39.组合总和【medium】

题目链接:力扣39.组合总和
在这里插入图片描述
视频链接:代码随想录
题解链接:灵茶山艾府

1、思路

  • 和组数总和Ⅲ有点类似,那边数组的长度是固定的并且不可以重复,这便是不固定可以重复使用,这边也多设置一个参数left表示还差多少完成target的目标。
  • 终止条件:当left == 0就可以存储答案
  • 单层逻辑:选或者不选,**注意这边选的话递归还是本次的i,**由于是可以重复使用的,这和之前的回溯递归不一样的地方
  • 时间复杂度: O ( n ∗ 2 n ) O(n*2^n) O(n2n) ,注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此

2、代码

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        ans = []
        path = []

        def dfs(i:int, left:int):
            if left == 0:
                ans.append(path.copy())
                return
            if i == len(candidates) or left < 0:
                return
            # 不选i
            dfs(i+1, left)

            path.append(candidates[i])
            # 这边允许元素重复使用,所以递归的话是i而不是i+1
            dfs(i, left - candidates[i]) # 这边i不是数本身,而是数组的索引
            path.pop()
        
        dfs(0, target)
        return ans
        

三、力扣40.组数总和Ⅱ【medium】

题目链接:力扣40.组数总和Ⅱ
在这里插入图片描述

视频链接:代码随想录
题解链接:灵茶山艾府

1、思路

  • 本题和39题的区别是,我们的候选仓库 candidates 中的元素是可以重复的,里面的元素却不可以重复使用,
  • 所以实际上这边不选某个元素的时候要特殊处理,比上一题来的复杂
  • 我们可以先排序一下,比较好处理重复元素,并且可以做到一个优化剪枝
  • 时间复杂度: O ( n ∗ 2 n ) O(n*2^n) O(n2n) ,注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此

2、代码

"""
注意:本题和39题的区别是,我们的候选仓库 candidates 中的元素是可以重复的,里面的元素却不可以重复使用,
      所以实际上这边不选某个元素的时候要特殊处理,比上一题来的复杂
"""
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort() # 先从小到大排个序
        n = len(candidates)
        ans = []
        path = []

        def dfs(i:int, left:int):
            if left == 0:
                ans.append(path.copy())
                return

            if i == len(candidates):
                return

            x = candidates[i]
            if left < x:
                return

            path.append(x)
            dfs(i+1, left - x)
            path.pop()

            # 不选 x,那么后面所有等于 x 的数都不选
            # 如果不跳过这些数,会导致「选 x 不选 x'」和「不选 x 选 x'」这两种情况都会加到 ans 中,这就重复了
            i += 1
            while i < n and candidates[i] == x:
                i += 1
            dfs(i,left)

        dfs(0,target)
        return ans

3、代码问题

  • 这边我一开始只注意到不可以重复使用,直接在上一题的基础上快速修改了,就报错了……
  • 这道题的元素仓库本来就有重复的元素,所以我需要处理这个细节,否则我的ans中就有很多重复答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值