12.5 六问诊断法:面对陌生问题如何系统推进
核心问题:面对陌生问题如何系统推进?
直觉:像飞行员起飞前跑清单——逐项检查,不漏环节,不靠"灵感"。
一、问题情境:War Story——从迷茫到六问
想象你是一个新手程序员,面试官给你一个问题:
"给定一个字符串,找出最长回文子串。"
你脑袋一片空白:这是什么问题?该用什么方法?
常见错误反应:
- 等待灵感:盯着屏幕发呆,期望"突然想到"解法
- 乱试方法:想到一个方法就直接写代码,不管是否适用
- 查搜索引擎:直接搜答案,不理解原理
正确反应:像飞行员一样,跑一个清单——六问清单。
二、直觉引入:试飞员心态
2.1 为什么需要六问?
问题:新手面对陌生问题时,容易:
- 遗漏关键信息(没看清约束条件)
- 选错范式(没判断清楚就用方法)
- 跳过验证(写完代码不测试)
解决:一个系统化的"检查清单",强制你走完关键步骤。
2.2 试飞员心态
飞行员起飞前不会靠"感觉",而是逐项执行清单:
起飞前检查清单:
✅ 燃油量够吗?
✅ 仪表盘正常吗?
✅ 天气条件允许吗?
✅ 襟翼位置正确吗?
...算法设计也一样:
六问诊断清单:
Q1: 我真的理解问题吗?
Q2: 有没有简单正确的算法?
Q3: 我的问题在已知目录中吗?
Q4: 特殊情况是否可解?
Q5: 哪种范式最像?
Q6: 还是想不出怎么办?2.3 六问清单详解
| 问题 | 目的 | 关键词 | 典型输出 |
|---|---|---|---|
| Q1 | 理解问题 | 理解 | 输入输出约束明确 |
| Q2 | 简单基线 | 基线 | 暴力解法 + 瓶颈分析 |
| Q3 | 查目录 | 目录 | 问题类型匹配 |
| Q4 | 特殊情况 | 特殊化 | 简化版本启发 |
| Q5 | 范式匹配 | 范式 | 候选方案 + 复杂度比较 |
| Q6 | 循环修正 | 迭代 | 反例测试 + 重新建模 |
三、算法定义:六问诊断法形式化
3.1 六问流程图
输入:问题描述
↓
┌──────────────────────┐
│ Q1: 理解问题 │
│ 输入?输出?约束? │
└──────────────────────┘
↓
┌──────────────────────┐
│ Q2: 简单算法 │
│ 暴力基线 + 瓶颈 │
└──────────────────────┘
↓
┌──────────────────────┐
│ Q3: 查目录 │
│ 匹配问题类型 │
└──────────────────────┘
↓
┌──────────────────────┐
│ Q4: 特殊情况 │
│ 参数固定、规模缩小 │
└──────────────────────┘
↓
┌──────────────────────┐
│ Q5: 范式匹配 │
│ 候选方案 + 复杂度比较 │
└──────────────────────┘
↓
┌──────────────────────┐
│ Q6: 循环修正 │
│ 反例测试、重新建模 │
└──────────────────────┘
↓
输出:推荐方案 + 待确认点3.2 六问详解
Q1:我真的理解问题吗?
检查清单:
Q1 检查:
├── 输入是什么?(类型、规模、格式)
├── 输出是什么?(形式、要求)
├── 约束是什么?(时间、空间、正确性)
├── 有小例子吗?(手动计算验证)
└── 有等价表述吗?(重述问题)常见错误:
- 混淆输入输出
- 忽略隐式约束
- 未构造小例子
Q2:有没有简单正确的算法?
检查清单:
Q2 检查:
├── 暴力解法是什么?(枚举所有可能性)
├── 暴力解法的复杂度?
├── 暴力解法慢在哪里?
└── 有启发式基线吗?意义:
- 验证问题理解是否正确
- 作为正确性对照
- 发现瓶颈所在
Q3:我的问题在已知目录中吗?
检查清单:
Q3 检查:
├── 能否映射到已知问题?
├── 是否是已知问题的变体?
└── 是否组合多个已知问题?
已知问题目录:
├── 排序问题(Ch3)
├── 查找问题(Ch3)
├── 图问题(Ch7)
│ ├── 遍历(BFS/DFS)
│ ├── 最短路径
│ ├── MST
│ ├── 拓扑排序
│ └── 流问题
├── 组合搜索(Ch5回溯)
├── 优化问题(Ch6 DP, Ch7贪心)
├── NP完全问题(Ch8)
├── 分布式问题(Ch9)
├── 流式问题(Ch10)
└── 检索融合问题(Ch11)Q4:特殊情况是否可解?
检查清单:
Q4 检查:
├── 固定一个参数,问题是否变简单?
├── 缩小规模,能否看清结构?
├── 删掉一个约束,能否求解?
└── 特殊输入(有序、空、单元素)?意义:
- 发现问题的隐藏结构
- 获得启发和洞察
- 为推广做准备
Q5:哪种范式最像?
检查清单:
Q5 检查:
├── 能分解? → 分治(子问题独立)
├── 能贪心? → 需证明无后效性
├── 有重叠子问题? → DP
├── 无有效分解? → 回溯/搜索
├── NP完全? → 近似/启发式
└── 规模极大? → 分布式/流式
对比方法:
├── 每种范式给出一个候选方案
├── 分析各方案的复杂度
└── 选择最优适配Q6:还是想不出怎么办?
检查清单:
Q6 检查:
├── 回到问题定义,重新审视
├── 构造反例,检验假设
├── 查阅文献/教程
├── 请教专家或同伴
└── 让Agent生成候选方案,逐一审查
Agent协作模式:
├── 输入问题描述
├── Agent走六问流程
├── 输出结构化诊断报告
├── 人工审查每步判断
└── 修正并迭代3.3 六问与前章节的映射
| 六问步骤 | 关联章节 | 关联知识点 |
|---|---|---|
| 理解问题 | Ch1 | 问题定义、输入输出、约束 |
| 简单算法 | Ch1-Ch12 | 暴力基线、启发式 |
| 查目录 | Ch2-Ch11 | 问题分类、范式识别 |
| 特殊情况 | Ch5-Ch8 | 参数固定、规模缩小 |
| 范式匹配 | Ch3分治, Ch5回溯, Ch6DP, Ch7贪心 | 四大范式选择 |
| 循环修正 | Ch1 | 六问循环、Agent协作 |
四、实现示例:最长回文子串的六问诊断
让我们用六问诊断法分析"最长回文子串"问题。
4.1 问题陈述
问题描述:
给定一个字符串s,找出其中最长的回文子串。
回文:正读反读都相同的字符串。
子串:字符串的连续片段。4.2 六问诊断过程
Q1:理解问题
输入:字符串s(长度n)
输出:最长回文子串(字符串)
约束:
├── 时间限制:O(n²)可接受,O(n³)可能太慢
├── 空间限制:O(n)或O(n²)可接受
└── 正确性:必须是最长,且是回文
小例子:
s = "babad"
回文子串:"bab"、"aba"、"a"、"b"、"d"
最长:"bab"或"aba"(长度3)
等价表述:
找字符串中满足s[i:j]是回文且长度最大的子串Q2:简单算法
暴力解法:
枚举所有子串,检查是否回文,找最长的
复杂度:
├── 子串数:O(n²)
├── 回文检查:O(n)
└── 总复杂度:O(n³)
瓶颈:
回文检查需要O(n)时间,重复计算很多次
基线验证:
n=5时:O(125)可行
n=1000时:O(10^9)太慢Q3:查目录
问题类型匹配:
├── 字符串问题(Ch3扩展)
├── 最优化问题(Ch6 DP)
├── 搜索问题(回溯扩展)
└── 不是排序、查找、图的直接应用
候选范式:
├── DP:有可能(重叠子问题)
├── 分治:不适合(子问题不独立)
├── 贪心:不适合(无贪心选择性质)
└── 回溯:可行(枚举所有回文子串),但效率不如DPQ4:特殊情况
固定参数:
├── 单字符:总是回文(长度1)
├── 双字符:相邻相同才回文
└── 已知对称性:可利用
缩小规模:
├── 先看小例子:"a"、"aa"、"aba"
└── 发现规律:回文有对称中心
启发:
回文可以看作"从中心向外扩展"Q5:范式匹配
候选方案:
方案1:DP
状态:dp[i][j] = s[i:j]是否是回文
转移:dp[i][j] = (s[i]==s[j]) and (j-i<=2 or dp[i+1][j-1])
复杂度:O(n²)时间,O(n²)空间
方案2:中心扩展
思路:枚举中心,向两边扩展直到不回文
复杂度:O(n²)时间,O(1)空间
方案3:Manacher算法(高级)
思路:利用对称性,避免重复检查
复杂度:O(n)时间,O(n)空间
推荐方案:
├── 简单场景:中心扩展(O(n²),空间O(1))
├── 一般场景:DP(O(n²),空间O(n²),易理解)
└── 高级场景:Manacher(O(n),复杂)Q6:循环修正
测试方案1(DP):
小例子验证:s="babad"
dp[0][2] = (b==b) and (2-0<=2) = True ✓
dp[1][3] = (a==a) and (3-1<=2) = True ✓
dp[0][4] = (b==d) = False
反例测试:
s="cbbd"
dp[1][2] = (b==b) and True = True ✓(回文"bb")
最长回文是"bb",长度2
结论:DP方案正确 ✓4.3 六问诊断报告
┌─────────────────────────────────────┐
│ 问题理解: │
│ 输入:字符串s(长度n) │
│ 输出:最长回文子串 │
│ 约束:O(n²)时间可接受 │
│ 简单基线: │
│ 方案:枚举所有子串检查回文 │
│ 瓶颈:回文检查O(n),总O(n³) │
│ 候选范式: │
│ [DP, 中心扩展, Manacher] │
│ 选择理由:重叠子问题适合DP │
│ 复杂度比较: │
│ DP:O(n²)时间,O(n²)空间 │
│ 中心扩展:O(n²)时间,O(1)空间 │
│ Manacher:O(n)时间,O(n)空间 │
│ 推荐方案: │
│ 一般场景:中心扩展 │
│ 需最优效率:Manacher │
│ 反例与边界: │
│ 已验证小例子 │
│ 仍需人工确认: │
│ Manacher实现复杂度 │
└─────────────────────────────────────┘五、与前章节关联:六问贯穿全书
5.1 六问与全书章节对应
| 六问 | 对应章节 | 教学内容 |
|---|---|---|
| Q1:理解问题 | Ch1开场 | 输入输出约束定义 |
| Q2:简单算法 | Ch3排序基线 | 暴力解法+瓶颈分析 |
| Q3:查目录 | Ch4图+Ch5回溯+Ch6DP+Ch7贪心 | 问题类型识别 |
| Q4:特殊情况 | Ch8NP完全 | 参数固定、规模限制 |
| Q5:范式匹配 | Ch3-Ch11综合 | 范式选择决策树 |
| Q6:循环修正 | 全书迭代案例 | 反例构造、重新建模 |
5.2 六问法作为全书主线
六问法不是"最后一章才出现"的方法,而是贯穿全书的思维框架:
- Ch1:Q1理解问题的基础
- Ch2:数据结构选择影响Q5范式匹配
- Ch3:分治原型,Q5候选之一
- Ch4-Ch7:贪心、图算法原型,Q5候选
- Ch5:回溯原型,Q5候选之一
- Ch6:DP原型,Q5候选之一
- Ch8:NP完全性,Q4特殊情况分析
- Ch9-Ch10:分布式、流式,Q5扩展候选
- Ch11:Transformer视角,LLM时代的新范式
- Ch12:六问法综合,系统化凝练
六、设计反思:策略先于战术
6.1 策略 vs 战术
策略(Strategy):做什么——选择范式、选择数据结构
战术(Tactics):怎么做——实现细节、代码优化、边界处理
重要原则:策略错误,再好的战术也救不了。
例子:
问题:最长递增子序列(LIS)
错误策略:用贪心
战术:优化贪心选择规则、优化代码
结果:无论如何优化,贪心都是错的(不能保证最优)
正确策略:用DP
战术:朴素DP实现、空间优化
结果:保证最优解6.2 从简单到正确到高效
三层递进:
第一层:简单
├── 暴力解法
├── 正确性验证
└── 理解瓶颈
第二层:正确
├── 选择正确范式
├── 设计正确算法
└── 构造反例验证
第三层:高效
├── 优化复杂度
├── 空间优化
└── 常数因子优化不可跳过第二层——先保证正确,再追求高效。
七、知识卡片
C12-33 六问诊断法
直觉解释:像医生看病,按清单逐项检查,不漏环节。
核心内容:Q1理解问题 → Q2简单算法 → Q3查目录 → Q4特殊情况 → Q5范式匹配 → Q6循环修正。
关键特征:系统化推进,每步都有明确目标,避免"等灵感"。
常见误解:
- ❌ 六问法是线性流程——实际会回环迭代
- ✅ 正确理解:六问法是循环,可能回到前面的步骤
实例:最长回文子串——按六问逐步推进,发现中心扩展法和Manacher算法。
C12-34 Q1: 理解问题
直觉解释:先搞清楚"要什么",别急着动手。
核心内容:明确输入输出、约束条件、构造小例子验证理解。
关键特征:不要模糊理解就开始写代码,小例子能发现隐藏约束。
常见误解:
- ❌ 理解问题很简单——复杂问题的输入输出约束很隐蔽
- ✅ 正确理解:仔细阅读问题描述,构造小例子验证
实例:二分查找——理解"有序数组"的约束是关键前提。
C12-35 Q2: 简单算法
直觉解释:先用"笨办法"做出来,别想一步到位。
核心内容:给出暴力基线方案,分析瓶颈在哪里。
关键特征:基线方案不一定高效,但能验证理解和发现瓶颈。
常见误解:
- ❌ 简单算法没用——它是正确性验证和优化起点
- ✅ 正确理解:基线方案是优化的起点
实例:排序问题——暴力O(n²),分析瓶颈是"比较太多",引出分治优化。
C12-36 Q3: 查目录
直觉解释:翻开"知识库",看问题属于哪类。
核心内容:识别问题类型:排序/搜索/图/字符串/动态规划...
关键特征:问题类型匹配算法范式的知识库。
常见误解:
- ❌ 问题都能归入某一类——有些问题是混合类型,需要拆解
- ✅ 正确理解:可能需要组合多种范式
实例:最短路径——查目录发现图论问题,候选范式:贪心(Dijkstra)/DP(Bellman-Ford)。
C12-37 Q4: 特殊情况
直觉解释:参数固定或规模缩小,问题变简单。
核心内容:固定某些参数、限制规模、考虑退化情况。
关键特征:特殊参数可能让NP问题变成P问题。
常见误解:
- ❌ 特殊情况不重要——它们常是突破口或边界验证
- ✅ 正确理解:特殊情况能发现隐藏结构
实例:图着色问题——k=2时多项式可解(二分图判断),k≥3时NP完全。
C12-38 Q5: 范式匹配
直觉解释:从候选方案中选最适合的。
核心内容:分治/贪心/DP/回溯/近似的选择依据和复杂度比较。
关键特征:范式选择依赖问题特征:子问题是否独立?是否有贪心选择性质?是否有重叠子问题?
常见误解:
- ❌ 每个范式对应一类问题——很多问题多种范式都适用,需比较复杂度
- ✅ 正确理解:多种范式可能都适用,需对比选择
实例:背包问题——贪心(近似)、DP(精确)、回溯(求所有解)。
C12-39 Q6: 循环修正
直觉解释:方案不是一次对的,要反复检查。
核心内容:构造反例测试、参考专家方案、必要时重新建模。
关键特征:反例是发现设计漏洞的利器,专家方案提供验证。
常见误解:
- ❌ 一次设计就正确——复杂问题需要多次迭代修正
- ✅ 正确理解:迭代修正直到正确
实例:贪心策略初稿→发现反例→修正策略或换范式。
C12-40 试飞员心态
直觉解释:像飞行员起飞前跑清单,不要靠"感觉"。
核心内容:系统性思考(按六问清单) > 灵感式思考(等想法)。
关键特征:结构化方法保证覆盖关键环节,避免遗漏。
常见误解:
- ❌ 方法论限制创造力——方法论是基础,创造力在此基础上发挥
- ✅ 正确理解:方法论是框架,创造力在框架内发挥
实例:资深程序员也有Checklist(代码审查清单、部署清单)。
八、练习设计
基础练习(理解六问)
练习 12.5-1:用六问诊断法分析"最长公共子序列(LCS)"问题。
参考答案:
Q1: 理解问题
输入:两个字符串X和Y(长度m和n)
输出:最长公共子序列的长度(或子序列本身)
约束:O(mn)时间可接受
小例子:X="ABC", Y="AC" → LCS="AC"长度2
Q2: 简单算法
暴力解法:枚举X的所有子序列,检查是否是Y的子序列,找最长的
复杂度:O(2^m × n) —— 指数爆炸
瓶颈:子序列数是O(2^m)
Q3: 查目录
问题类型:字符串问题、最优化问题
候选范式:DP(重叠子问题)
Q4: 特殊情况
固定参数:单字符匹配
启发:LCS(X,Y)可能依赖LCS(X前缀,Y前缀)
Q5: 范式匹配
候选方案:DP
状态:dp[i][j] = X前i字符与Y前j字符的LCS长度
转移:dp[i][j] = max(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]+1 if X[i]==Y[j])
复杂度:O(mn)时间,O(mn)空间(可优化到O(min(m,n)))
Q6: 循环修正
小例子验证:X="ABC", Y="AC"
dp[1][1] = (A==A) → dp[0][0]+1 = 1 ✓
dp[2][2] = (B!=C) → max(dp[1][2], dp[2][1]) = 1 ✓
dp[3][2] = (C==C) → dp[2][1]+1 = 2 ✓
结论:DP方案正确 ✓进阶练习(六问应用)
练习 12.5-2:用六问诊断法分析"三数之和"问题。
思路:
Q1: 理解问题
输入:数组nums,目标target
输出:三个数的下标,使其和=target
约束:无重复解,O(n²)时间可接受
Q2: 简单算法
暴力解法:三重循环枚举所有三元组
复杂度:O(n³)
瓶颈:三重循环
Q3: 查目录
问题类型:数组问题、查找问题
候选范式:排序+双指针(贪心扩展)
Q4: 特殊情况
固定一个数:变成"两数之和"问题
启发:两数之和有O(n)解法(哈希表)
Q5: 范式匹配
候选方案:
├── 排序+双指针:O(n²)
└── 固定一个数+哈希表:O(n²)
推荐方案:排序+双指针(空间O(1))
Q6: 循环修正
小例子验证:nums=[-1,0,1,2,-1,-4], target=0
排序后:[-4,-1,-1,0,1,2]
双指针找到:[-1,-1,2]和[-1,0,1]
结论:方案正确 ✓设计练习(六问→Agent)
练习 12.5-3:设计六问诊断Agent Skill的伪代码。
思路:
def six_question_diagnosis_agent(problem_description):
"""
六问诊断Agent Skill
输入:问题描述文本
输出:结构化诊断报告
"""
report = {}
# Q1: 理解问题
report['Q1'] = {
'input': parse_input(problem_description),
'output': parse_output(problem_description),
'constraints': parse_constraints(problem_description),
'examples': generate_examples(problem_description)
}
# Q2: 简单算法
baseline = generate_baseline_solution(report['Q1'])
report['Q2'] = {
'baseline_solution': baseline,
'baseline_complexity': analyze_complexity(baseline),
'bottleneck': identify_bottleneck(baseline)
}
# Q3: 查目录
problem_type = match_problem_type(report['Q1'])
report['Q3'] = {
'problem_type': problem_type,
'related_chapters': get_related_chapters(problem_type)
}
# Q4: 特殊情况
special_cases = generate_special_cases(report['Q1'])
report['Q4'] = {
'special_cases': special_cases,
'insights': analyze_special_cases(special_cases)
}
# Q5: 范式匹配
candidates = generate_paradigm_candidates(report['Q3'])
report['Q5'] = {
'candidates': candidates,
'complexity_comparison': compare_complexity(candidates),
'recommendation': select_best_candidate(candidates)
}
# Q6: 循环修正
report['Q6'] = {
'test_cases': generate_test_cases(report['Q5']['recommendation']),
'need_human_review': identify_review_points(report)
}
return report开放练习
练习 12.5-4:六问法如何与LLM协同工作?
思考方向:
LLM擅长:
├── Q1理解问题:解析文本,提取输入输出约束
├── Q2简单算法:生成基线方案代码
├── Q3查目录:检索知识库,匹配问题类型
LLM不擅长:
├── Q4特殊情况:判断哪些特殊情况有意义
├── Q5范式匹配:复杂度比较需要精确分析
├── Q6循环修正:构造反例需要创造性思维
人机协作分工:
├── LLM:Q1-Q3自动化
├── 人:审查LLM输出,补充Q4-Q6
└── 循环:人反馈修正→LLM更新诊断
协同模式:
├── LLM生成诊断报告初稿
├── 人审查并标注待确认点
├── LLM根据反馈迭代修正
└── 人最终确认方案导航:上一节:12.4 回溯回顾 | 下一节:12.6 综合练习 →