news 2026/2/17 2:06:56

代码随想录算法训练营第二十一天| 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/2/16 9:27:47

Git commit签名验证确保GLM-4.6V-Flash-WEB代码来源可信

Git Commit 签名验证确保 GLM-4.6V-Flash-WEB 代码来源可信 在 AI 模型开源浪潮席卷全球的今天&#xff0c;一个看似不起眼的技术细节&#xff0c;正悄然决定着整个生态的安全底线——你从仓库克隆下来的那行代码&#xff0c;真的来自它声称的开发者吗&#xff1f;尤其当这个模…

作者头像 李华
网站建设 2026/2/7 19:48:33

跨语言高效算法实现与调优实践:Python、Go、Java、C++综合案例解析

在互联网开发中&#xff0c;高效算法的实现直接影响系统性能和用户体验。不同编程语言在算法执行和并发处理上各有特点。本文将通过具体实例&#xff0c;展示如何在 Python、Go、Java 和 C 中实现高效排序、搜索和并发任务&#xff0c;并对性能优化做一些思考。一、Python&…

作者头像 李华
网站建设 2026/2/11 16:45:07

Git commit钩子校验GLM-4.6V-Flash-WEB提交代码质量

Git Commit钩子校验GLM-4.6V-Flash-WEB提交代码质量 在如今AI应用快速落地的背景下&#xff0c;一个模型“能跑”只是起点&#xff0c;真正决定产品成败的是它能否稳定、高效、可持续地运行。尤其是在基于多模态大模型构建Web服务时&#xff0c;哪怕是一行缩进错误或一次配置疏…

作者头像 李华
网站建设 2026/2/7 1:37:50

Chromedriver下载地址不稳定?使用GLM-4.6V-Flash-WEB离线推理模式

Chromedriver下载地址不稳定&#xff1f;使用GLM-4.6V-Flash-WEB离线推理模式 在现代自动化测试和爬虫开发中&#xff0c;一个看似简单却频繁出现的问题正困扰着无数工程师&#xff1a;Chromedriver 下载失败。 无论是 CI/CD 流水线突然中断&#xff0c;还是本地脚本因“连接超…

作者头像 李华
网站建设 2026/2/7 0:40:33

C#调用GLM-4.6V-Flash-WEB模型接口:Windows平台开发指南

C# 调用 GLM-4.6V-Flash-WEB 模型接口&#xff1a;Windows 平台开发实践 在企业级智能系统日益普及的今天&#xff0c;如何让传统业务软件“看懂”图像内容&#xff0c;已成为办公自动化、文档处理和智能客服等领域的重要课题。许多开发者面临这样的困境&#xff1a;已有成熟的…

作者头像 李华
网站建设 2026/2/16 13:36:51

MyBatisPlus整合GLM-4.6V-Flash-WEB后端服务:数据库+AI双驱动

MyBatisPlus整合GLM-4.6V-Flash-WEB后端服务&#xff1a;数据库AI双驱动 在如今这个内容爆炸、图像泛滥的互联网时代&#xff0c;用户上传一张图&#xff0c;系统不仅要“看见”&#xff0c;还得“看懂”。传统Web后端擅长处理结构化数据——比如订单、用户信息、日志记录&…

作者头像 李华