news 2026/4/16 16:37:24

代码随想录算法训练营第二十一天| 77. 组合、216.组合总和III、17.电话号码的字母组合

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
代码随想录算法训练营第二十一天| 77. 组合、216.组合总和III、17.电话号码的字母组合

77. 组合

目标:
给定两个整数nk,返回[1, n]中所有可能的k个数的组合。

  • 组合:只关心选了哪些数
  • 不关心顺序:[1,2][2,1]是同一个组合

核心思路

这是一个经典回溯 / DFS 组合题。

为什么用回溯?

  • 我们在做的是:
    👉从 1~n 中“选 k 个”

  • 每个数:

    • 要么选
    • 要么不选
  • 但为了避免重复(比如[2,1]),必须保证递增顺序

回溯模型(记这个模板)

路径 path:当前已经选的数 起点 start:下一步可以选的最小数字(保证不回头) 终止条件:path 长度 == k

决策过程:

  • [start, n]范围内枚举下一个数字
  • 对每个数字:
    1. 选择它,加入path
    2. 递归继续选择下一个(start更新为当前数字 + 1)
    3. 回溯:撤销本次选择,尝试其他数字

代码

fromtypingimportListclassSolution:defcombine(self,n:int,k:int)->List[List[int]]:res=[]# 最终结果:存所有长度为 k 的组合path=[]# 当前正在构建的组合(回溯路径)defbacktracking(start):""" start:当前层允许选择的最小数字 保证组合中的数字递增,避免重复 """# 终止条件:已经选了 k 个数iflen(path)==k:# 注意:必须拷贝 path# 因为 path 会在回溯过程中被不断修改res.append(path[:])return# 在当前层,从 start 到 n 依次尝试每一个可能的选择foriinrange(start,n+1):# 1️⃣ 做选择:把当前数字加入路径path.append(i)# 2️⃣ 进入下一层递归# 下一层只能从 i + 1 开始选,不能回头backtracking(i+1)# 3️⃣ 撤销选择(回溯)# 把刚刚加进去的数字移除,尝试下一个 ipath.pop()# 从 1 开始尝试选第一个数backtracking(1)returnres
指标复杂度说明
时间复杂度O(C(n, k) · k)一共生成 C(n, k) 个组合,每个组合拷贝 k 个元素
空间复杂度O(k)递归调用栈 + 当前 path 的最大长度

77剪枝做法

在已经不可能选满 k 个数的情况下,提前停止递归。

为什么 77 可以剪枝?

在某一层:

  • 已经选了len(path)个数

  • 还需要选:

    k-len(path)

    而当前能用的数字是:

    [start,start+1,...,n]

    数量是:

    n-start+1

    ❗ 如果:

    剩余可选数量 < 还需要选的数量

    👉 这条路不可能成功,直接停

剪枝怎么体现在代码里?

原来的 for 循环

foriinrange(start,n+1):

剪枝后的 for 循环(核心)

foriinrange(start,n-(k-len(path))+2):


如何理解:

我要给未来留下 足够的位置,让后面还能选满 k 个。

  • 当前选i
  • 后面最多还能选:
n-i

所以i最大只能到:

n-(k-len(path))+1

Python 的range右端不包含 → 再+1

代码

classSolution:defcombine(self,n:int,k:int):res=[]path=[]defbacktracking(start):# 如果已经选满 k 个,记录结果iflen(path)==k:res.append(path[:])return# 剪枝:# 剩余可选数量 = n - start + 1# 需要选的数量 = k - len(path)# 当剩余 < 需要时,for 循环根本不会进入foriinrange(start,n-(k-len(path))+2):path.append(i)backtracking(i+1)path.pop()backtracking(1)returnres
指标复杂度说明
时间复杂度O(C(n, k) · k)最终仍然要生成所有合法组合;剪枝只是减少无效搜索路径
空间复杂度O(k)递归调用栈深度最多为 k,path长度最多为 k

216.组合总和III

目标:
1 到 9 中选 k 个不同的数,使它们的和等于 n,返回所有组合。

  • 每个数字只能用一次
  • 组合(不关心顺序)
  • 数字范围固定是 1~9

核心思路

这是「固定长度 k 的组合」+「有目标和 n」
回溯设计(三要素):

  1. path
    • 当前选中的数字
    • 同时决定:
      • 已选个数len(path)
      • 当前和cur_sum
  2. start
    • 下一步允许选择的最小数字
    • 保证递增,避免重复
  3. 终止条件
    • 如果len(path) == k
      • cur_sum == n→ 记录结果
      • 否则 → 直接返回

代码

fromtypingimportListclassSolution:defcombinationSum3(self,k:int,n:int)->List[List[int]]:res=[]# 存放所有满足条件的组合path=[]# 当前正在构建的组合defbacktracking(start):""" start: 当前层允许选择的最小数字 保证数字递增,避免重复使用同一个数字 """# 如果已经选了 k 个数iflen(path)==k:# 只在这里检查组合的和是否等于 nifsum(path)==n:# 必须拷贝 path,否则回溯时会被修改res.append(path[:])return# 从 start 到 9 依次尝试选择数字foriinrange(start,10):# 做选择:加入当前数字path.append(i)# 进入下一层,下一层只能选更大的数字backtracking(i+1)# 撤销选择(回溯)path.pop()# 从 1 开始尝试选第一个数字backtracking(1)returnres
指标复杂度说明
时间复杂度O(C(9, k) · k)枚举所有长度为 k 的组合,每次计算sum(path)需要 O(k)
空间复杂度O(k)递归调用栈深度最大为 k,path长度最多为 k

216剪枝

✂️ 剪枝 1:和已经超过 n,直接停cur_sum > n

defbacktracking(start,cur_sum):ifcur_sum>n:# ✅ 剪枝 1return

如果当前路径的和已经大于目标 n,后面只会加更大的数,不可能成功 → 直接返回。

✂️ 剪枝 2:for 循环里提前 breaklen(path) > k

foriinrange(start,10):ifcur_sum+i>n:# ✅ 剪枝 2break

这行为什么是 break?

  • i是递增的
  • cur_sum + i已经超了
  • 后面的i+1,i+2… 只会更大

👉 这一整段 for 都没必要继续

剪枝 1 和 剪枝 2 本质上在“做同一件事”(防止和超过 n),
但作用位置不同,功能并不完全重复。

👉 实际写代码时:二选一就够了
👉 最推荐:只保留剪枝 2

✂️ 剪枝 3:剩余数量不够凑满 k

# 还需要选的数量need=k-len(path)# 剩余可选数字数量remain=9-start+1ifremain<need:# ✅ 剪枝 3return

剩下的数字数量,已经不够凑满 k 个 👉 再选也没意义,直接停

代码

classSolution:defcombinationSum3(self,k:int,n:int):res=[]path=[]defbacktracking(start,cur_sum):# 🔴 剪枝 1:和已经超过 nifcur_sum>n:return# 选满 k 个数iflen(path)==k:ifcur_sum==n:res.append(path[:])return# 🔴 剪枝 3:剩余数字不够if9-start+1<k-len(path):returnforiinrange(start,10):# 🔴 剪枝 2:for 循环提前结束ifcur_sum+i>n:breakpath.append(i)backtracking(i+1,cur_sum+i)path.pop()backtracking(1,0)returnres
指标复杂度说明
时间复杂度O(C(9, k) · k)枚举所有合法组合,9 是常数
空间复杂度O(k)递归深度 + path 长度

17.电话号码的字母组合

给一个字符串digits(只包含 2-9),每个数字对应手机九宫格字母,比如:

  • 2: abc
  • 3: def

  • 把所有可能的字母组合都列出来。

例:
"23"["ad","ae","af","bd","be","bf","cd","ce","cf"]
空输入""→ 返回[]

核心思路(回溯/DFS)

  1. 用 dict 存数字 → 字母

    2->abc3->def...
  2. 从左到右处理 digits

    • 第 1 个数字选一个字母
    • 第 2 个数字再选一个字母
    • 一直选到最后一位
  3. 每一位都把能选的字母“试一遍”

    • 字符串长度够了 → 存结果
    • 不够 → 继续拼

状态:

  • idx:当前处理到 digits 的第几位
  • path:当前拼出来的字符串(或字符数组)

代码

fromtypingimportListclassSolution:defletterCombinations(self,digits:str)->List[str]:# 如果输入是空字符串,没有任何组合# LeetCode 要求返回 []ifnotdigits:return[]# 数字到字母的映射表(手机九宫格)phone={"2":"abc","3":"def","4":"ghi","5":"jkl","6":"mno","7":"pqrs","8":"tuv","9":"wxyz"}res=[]# 最终结果:存所有字母组合path=[]# 当前正在拼的字符串(用 list,方便回溯)defbacktracking(idx):""" idx:当前处理到 digits 的第 idx 位 表示现在要给第 idx 个数字选一个字母 """# 如果已经处理完所有数字# 说明 path 中已经拼好了一个完整组合ifidx==len(digits):# 把当前拼好的字符列表转成字符串存起来res.append("".join(path))return# 取出当前数字对应的所有字母letters=phone[digits[idx]]# 依次尝试每一个字母forlinletters:# 1️⃣ 选择:把当前字母加入 pathpath.append(l)# 2️⃣ 进入下一位数字backtracking(idx+1)# 3️⃣ 撤销选择(回溯)# 换下一个字母继续试path.pop()# 从第 0 位数字开始处理backtracking(0)returnres
指标复杂度说明
时间复杂度O(4^m · m)一共有最多4^m种字母组合,每个组合长度为m,需要拼接成字符串
空间复杂度O(m)递归深度最多为mpath最多存m个字符
输出空间O(4^m · m)需要存所有结果字符串

假设:

  • m = len(digits)(digits 的长度)
  • 每个数字最多对应 4 个字母(7 和 9)


为什么时间复杂度是这样:

  • 第 1 位:最多 4 种选择
  • 第 2 位:最多 4 种选择
  • 总组合数:4 × 4 × … × 4 = 4^m
  • 每次存结果要"".join(path),长度是m

👉 所以是4^m × m

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 2:14:18

Git commit日志审查制度在GLM-4.6V-Flash-WEB社区的重要性

Git commit日志审查制度在GLM-4.6V-Flash-WEB社区的重要性 在AI大模型飞速发展的今天&#xff0c;一个开源项目的成败早已不再仅仅取决于模型本身的性能。技术可以复制&#xff0c;架构能够模仿&#xff0c;但真正难以被超越的&#xff0c;是一个项目背后所建立的工程文化与协…

作者头像 李华
网站建设 2026/4/15 13:56:18

CSDN官网技术帖精选:GLM-4.6V-Flash-WEB入门常见问题解答

GLM-4.6V-Flash-WEB 入门常见问题深度解析 在智能应用日益追求“看得懂、答得快”的今天&#xff0c;多模态大模型正从实验室走向真实业务场景。尤其是在电商、金融、客服等需要图文理解的领域&#xff0c;开发者不再满足于“模型能不能识别图像”&#xff0c;而是更关心&#…

作者头像 李华
网站建设 2026/4/15 20:04:14

让AI自己教自己写代码,会发生什么?

你有没有想过这样一个问题&#xff1a;如果把一个AI扔进GitHub的代码海洋里&#xff0c;不给它任何指导、不告诉它该做什么&#xff0c;它能自己学会写代码吗&#xff1f; 听起来像科幻小说的情节&#xff0c;但Meta FAIR的研究团队真的这么干了。更神奇的是&#xff0c;他们发…

作者头像 李华
网站建设 2026/4/16 15:33:58

Chromedriver下载地址更换频繁?内置GLM-4.6V-Flash-WEB解决方案

Chromedriver下载地址更换频繁&#xff1f;内置GLM-4.6V-Flash-WEB解决方案 在现代自动化测试的日常中&#xff0c;开发者常常遭遇一个看似“小问题”却极其烦人的挑战&#xff1a;Chromedriver版本不匹配、官方下载链接失效、镜像源频繁变动。尤其是在国内网络环境下&#xf…

作者头像 李华
网站建设 2026/4/16 9:07:56

UltraISO注册码最新版替代方案:用GLM-4.6V-Flash-WEB提升数据处理效率

GLM-4.6V-Flash-WEB&#xff1a;用轻量多模态模型重塑智能数据处理 在企业数字化转型加速的今天&#xff0c;我们正面临一个看似矛盾的需求&#xff1a;既要处理越来越多的非结构化数据&#xff08;如图像、截图、PDF&#xff09;&#xff0c;又要求系统具备更高的自动化与智能…

作者头像 李华
网站建设 2026/4/15 12:31:31

HTML viewport设置优化GLM-4.6V-Flash-WEB移动端展示

HTML viewport设置优化GLM-4.6V-Flash-WEB移动端展示 在智能手机几乎成为人体感官延伸的今天&#xff0c;用户对Web应用的交互体验要求早已超越“能用”层面。尤其是在多模态AI迅速落地的当下&#xff0c;一个视觉语言模型即便具备强大的图文理解能力&#xff0c;若其前端界面在…

作者头像 李华