力扣22.括号生成【medium】
力扣39.组合总和【medium】
力扣40.组数总和Ⅱ【medium】
一、力扣22.括号生成【medium】
题目链接:力扣22.括号生成
视频链接:灵茶山艾府
1、思路
-
可以理解成2n个位置选n个位置放置左括号
-
从输入的视角:枚举当前位置填左括号还是右括号?本质上是「选或不选」,把填左括号视作「选」,填右括号视作「不选」。(也可以反过来)。
-
时间复杂度: O ( n ∗ C m n ) O(n*C_m^n) O(n∗Cmn)
- 分析回溯问题的时间复杂度,有一个通用公式:路径长度×搜索树的叶子数。但由于左右括号的约束,实际上没有这么多叶子,根据 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(n4n)
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】
1、思路
- 和组数总和Ⅲ有点类似,那边数组的长度是固定的并且不可以重复,这便是不固定可以重复使用,这边也多设置一个参数
left
表示还差多少完成target
的目标。 - 终止条件:当
left == 0
就可以存储答案 - 单层逻辑:选或者不选,**注意这边选的话递归还是本次的
i
,**由于是可以重复使用的,这和之前的回溯递归不一样的地方 - 时间复杂度: O ( n ∗ 2 n ) O(n*2^n) O(n∗2n) ,注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
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(n∗2n) ,注意这只是复杂度的上界,因为剪枝的存在,真实的时间复杂度远小于此
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
中就有很多重复答案。