大家好,我是小彭。
2023 开年以来,全球媒体最火爆的热点莫过于一个生成式 AI 聊天机器人 —— ChatGPT,我们都被大量的信息刷屏了。在这些信息中,你或许看过这样一则新闻 《ChatGPT Passes Google Coding Interview for Level 3 Engineer With $183K Salary》,它说 ChatGPT 通过了谷歌工程师面试,则这个职位的年薪有 18.3 万美元。
让会不会让一些小伙伴产生焦虑的想法?
试想一下:我们要学习一个算法或者新知识新领域,直接将问题抛给 AI,让 AI 直接传道授业解惑,直接向你总结出全社会沉淀下来的最有价值的经验、方法论、内容或观点,你甚至都不需要去上学、找资料、看书。遇到不理解的地方可以继续向 AI 追问,AI 还会非常有耐心的帮你解释……
未来,固然需要想象。
现实,ChatGPT 能做到吗?
正好,最近有群友让我写一篇算法的入门文章,借此机会,就让我们来学习如何使用 ChatGPT 辅助算法学习:
在接下来的几篇文章中,小彭将继续为你介绍 AI 技术的使用攻略以及实践感悟。
如果你对 ChatGPT 还不够熟悉,希望我能够为你提供一些指引。
让我们开始吧!
今天文章比较长,写个简单的大纲:
1、LeetCode 算法题学习链路简要分析
2、ChatGPT 助手从入门到放弃
2.1 ChatGPT 的能力和限制
2.2 ChatGPT 的使用原则
3、面向 ChatGPT 编程的正确打开方式
4、总结
1. LeetCode 算法题学习链路简要分析
首先,请你思考:完整地学习一道算法题包含哪些步骤或动作:
- 步骤一:复制代码 🌚
- 步骤二:粘贴运行 🌚
- 步骤三:自我满足 🌚
说笑了,这么有任何价值(求饶),应该是:
- 阶段 1 - 白板编码
- 阶段 2 - 阅读优质题解
- 阶段 3 - 抽象问题模型
1.1 阶段 1 - 白板编码
这里所说的 “白板” 并不是真的在白板上写出答案,而是说在无外力辅助的环境下独立解出题目。这个阶段不仅仅是写代码,而是:
- 1.1 阅读: 快速阅读题目信息,提取出题目的关键信息,包括题目目标、关键约束、输入输出数据类型、数据量大小等;
- 1.2 抽象: 结合已掌握的算法知识抽象出题目的问题模型,并思考解决问题的算法,需注意算法复杂度能否满足问题的数据量上界;
- 1.3 编码: 题目一般有多种算法实现,优先写出复杂度最优的版本。如果做不到,则先写出最熟悉的算法,再优化为复杂度更优的算法(在面试和竞赛中策略类似);
- 1.4 检查: 检查题目条件并完善代码,包括问题约束、数组越界、大数越界、小数精度、初始状态值、目标状态值等;
- 1.5 调试: 重复第 1 - 4 个动作直到题目通过所有测试用例。
至此,你已经 “通过” 这道题(或许没有),然而你只是对已经学过的知识复习了一遍,对这部分算法更加熟悉了,但是并没有知识增量,所以你需要进入阶段 2:
1.2 阶段 2 - 阅读优质题解
阅读优质题解一直是最快的提升算法能力的途径,也是整个学习链路中最花时间的部分。好在社区中有非常多热爱算法的小伙伴,即使是刚刚发布的周赛题目,也不会出现没有题解的情况。
推荐一些优质算法题解作者:
因此,阅读题解阶段主要障碍就是看不懂,哪里看不懂呢?
- 难点 1 - 算法: 理解算法本身,文字、注释、代码、图表这些都是算法的表现形式;
- 难点 2 - 算法推导: 理解从题目一步步到算法的推导过程(有些题解会省略推导过程);
- 难点 3 - 算法证明: 理解算法的严格证明过程,特别是贪心算法(有些题解会省略证明过程)。
举个例子,题目 743. 网络延迟时间 有「 Dijkstra + 最小堆 」的算法,那么:
- 算法:理解 “每轮迭代从小顶堆中获取候选集里距离起点最短路长度最小的节点,并使用该点松弛相邻边” 就是理解算法本身;
- 算法推导:理解 “暴力 BFS/DFS → Floyd → 朴素 Dijkstra → Dijkstra + 最小堆” 就是理解算法推导过程;
- 算法证明:理解 “选择候选集中距离起点最短路长度最小的节点的方案不存在更优解” 就是理解算法证明过程。
有些题目会由多个解法,等于有多个算法、多个算法推导过程以及多个算法证明过程,那么你要理解的信息量是成倍增加的。这里要根据时间和优先级有所取舍。
理解了阅读题解阶段的主要障碍,那么这些障碍是由哪些原因导致的呢?
- 原因 1:题解结构缺失: 有的题解只提供了可运行的代码,但是省略了推导过程和证明过程,甚至没有复杂度分析(题解:没有复杂度分析我不完整了);
- 原因 2 - 思维复杂度过高: 有些题解结果完整,讲解也很详细,但是其中某些难点或某几行代码思维复杂度很高(大脑复杂度 O(nn)O(n^n)O(nn));
- 原因 3 - 前置知识缺失: 有些算法需要一些前置知识基础,例如 Dijkstra 算法就需要有图论基础(基础不牢地动山摇);
- 原因 4 - 代码语言缺失: 有些题解只会提供一种语言的代码(说的就是我);
在你阅读题解时,你还会尝试根据题解写出代码,相当于回退到阶段 1 的编码阶段,但这个阶段会尽可能多地借助外力辅助。 因为此时编码不是目的,是通过编码的方式加深对算法的理解。
至此,你不仅理解了题解的大部分信息,还手撸了一遍代码,恭喜你已经 “学会” 这道题,但是如果换一道变型题呢?这道题学会了,并不代表这一类的题目你都学会了,所以你需要进入阶段 3:
1.3 阶段 3 - 抽象问题模型
抽象问题模型就是你在阶段 1-2 抽象步骤中做的事情。
你之所以能在短时间解决算法问题, 是因为你曾经做过这道问题,或者曾经做过这类问题,曾经抽象过这类问题(天赋选手除外),这就是我们常说的多做多刷多总结。
所谓抽象,就是总结出题目的模型 / 套路,怎么做呢?
- 3.1 题目类型: 例如数学、双指针、回溯、动态规划、贪心、数据结构就是一级题目类型。继续细分下去,双指针又分为二分查找、滑动窗口、同向 / 相向双指针,动态规划又分为线性 DP、树形 DP、转压 DP、数位 DP 等,回溯又分为排列、组合、子集等等;
- 3.2 算法模型: 模型就是我们说的解题模板 / 板子。例如二分查找有排除严格不成立方向的模型,背包问题有 01 背包和完全背包和滚动数组的模型,线段树有朴素线段树 +Lazy 的模型,质数有暴力和筛法的模板等等;
- 3.3 编码技巧: 例如负数取模技巧、数组补 0 位技巧、链表 Dummy 节点技巧、除法转乘法技巧等等;
抽象题目模型需要建立在大量阅读量和刷题量的基础上,是最重要的环节。经过后文实验验证,目前 GPT-3.5 和 GPT-4 都无法达到顶尖算法高手的抽象水平。
最后,你还需要将整个思考过程按照题目编号记录下来,方便未来的你查阅,小彭就是简单记录在 Github 上。有时候我重新做一道题后,会发现今天写的代码质量比几个月前写的的代码质量高出很多,也在见证自己的成长。
至此,学习链路分析结束!
1.4 LeetCode 算法题学习链路小结
完成 LeetCode 算法题学习链路的简要分析后,用一个表格总结:
阶段 | 动作 | 描述 |
---|---|---|
1、白板编码 | 1.1 阅读 | 快速阅读题目信息,提取出题目的关键信息 |
1.2 抽象 | 结合已掌握的算法知识抽象出题目的问题模型 | |
1.3 编码 | 撸代码 | |
1.4 检查 | 检查题目条件并完善代码 | |
1.5 调试(for Loop) | 重复第 1 - 4 个动作直到题目通过所有测试用例 | |
2、阅读优质题解 | 2.1 理解算法 | 理解算法本身,文字、注释、代码、图表这些都是算法的表现形式 |
2.2 理解算法推导过程 | 理解从题目一步步到算法的推导过程 | |
2.3 理解算法证明过程 | 理解算法的严格证明过程,特别是贪心算法 | |
2.4 辅助编码(Async) | 撸代码 | |
3、抽象问题模型 | 3.1 抽象题目类型 | 例如数学、双指针、回溯、动态规划、贪心、数据结构 |
3.2 抽象算法模型 | 例如二分查找、背包问题、线段树 + Lazy、质数 | |
3.3 抽象编码技巧 | 例如负数取模技巧、数组补 0 位技巧、链表 Dummy 节点技巧、除法转乘法 |
接下来,我们开始思考如何将 ChatGPT 有机地融入到算法题的学习链路中:
2. ChatGPT 助手从入门到放弃
然而,随着问题的深入和复杂化,ChatGPT 的错误和问题也逐渐显露出来,这就是我从入门到 “放弃” 的整个过程:
2.1 向 ChatGPT 提问的一个误区
如果我们直接把问题交给 ChatGPT,试图让它直接输出详细的题解,会发生什么?
可以看到,这份回答有算法类型、算法描述、算法推导过程、算法实现和复杂度分析,算是一份相对完整的题解,但远远还谈不上优质,与 LeetCode 上优质的题解比相去甚远。
不是把 ChatGPT 吹到天上去了吗,问题出在哪里呢?
因为我们提出的一个开放(open-ended)问题,而这种提问方式对 ChatGPT 是低效的:
- 详细:怎么定义详细?100 个字还是 1000 个字叫详细?
- 优质:怎么定义优质?需要多少种算法,需要举一反三吗?
- 题解:怎么定义题解?需要包含哪些信息?
总之,“请你写出详细优质的题解”、“你能帮我做这道题吗” 和 “请你告诉我这道题” 这三种问法,在 ChatGPT 看来并没有本质区别,而且实测结果出奇地一致(请避免 😁)。
2.2 一个万能的 ChatGPT prompt 模板
在经过一整天被人工智障折磨,以及阅读 《The Art of ChatGPT Prompting》后,我总结出一个提问模板:
- 1、角色:限定知识领域(注意:New Bing 玩角色扮演出错概率偏高);
- 2、目标:一句话概括需要的帮助;
- 3、要求:对目标补充的具体清晰的要求,尽量表达清晰,避免模糊,一般采用 Do 和 Do not 格式;
- 4、举例(可选):输入输出样例。
后来我发现 OpenAI 在 GPT-4 的测试论文 《Sparks of Artificial General Intelligence: Early experiments with GPT-4》中,也采用了类似 “角色-目标 - 要求 - 举例” 的模板 ☺。
GPT-4 早期测试报告:https://arxiv.org/pdf/2303.12712.pdf
2.3 一个被逼疯的 prompt
我需要你担任算法教师和指导者,你需要给出一道 LeetCode 问题的代码和解题思路。
要求:
1、使用中文回答,使用 Kotlin 语言
2、代码必须带有注释
3、确保每个解法是严格不同的算法,并且每个解法包含算法名称、算法概括,算法详细描述、算法推导过程、代码实现、复杂度分析
4、先总结题目的解法个数,以及每个算法的算法名称
5、先输出复杂度最差的暴力解法,再依次输出复杂度更优的解法
6、不要输出题目描述和测试用例
题目:
718. 最长重复子数组
这道题有 4 种解法:
- 1、使用暴力解法,枚举所有子数组,判断是否为重复子数组。
- 2、使用动态规划解法,以二维数组 dp[i][j] 记录以A[i] 和 B[j] 为结尾的最长重复子数组长度。
- 3、使用滑动窗口解法,枚举 A 和 B 所有的对齐方式,计算每种对齐方式的最长重复子数组长度。
- 4、使用二分查找解法,最长重复子数组长度存在单调性,使用二分查找检查是否存在长度为 len 的重复子数组。
那 ChatGPT 会给出令人满意的答复吗?
可以看到:虽然回答比用 “请你写出详细优质的题解” 提问得到的回答优化了很多,但是连最简单的代码注释要求都没有满足,更不用说和 LeetCode 上的优质题解相比。
何苦看 ChatGPT❓
至于被爆吹的 New Bing 和 GPT-4 模型呢?实验结果和 GPT-3.5 没有明显差别(失望)。
GPT-4 实验结果:https://poe.com/s/VIkeeiqjDGVfuym0Kn5B
2.4 降低要求
在文章 《The Art of ChatGPT Prompting》中,提到:“It’s important to provide the ChatGPT with enough information to understand the context and purpose of the conversation, but too much information can be overwhelming and confusing.”
那么,有没有可能是因为我们提出的信息量太大,导致 ChatGPT 无法聚焦呢?
我们尝试让 ChatGPT 针对某个算法输出题解:
我需要你担任算法教师和指导者,你需要给出一道 LeetCode 问题的代码和解题思路。
要求:
1、使用中文回答,使用 Kotlin 语言;
2、使用二分查找+哈希表的解法
3、包含算法、算法概括,算法详细描述、算法推导过程、代码实现、复杂度分析
4、不要输出题目描述和测试用例
题目:
718. 最长重复子数组
大同小异,这也说明上一节的测试结果并不是因为要求过多导致。
2.5 重新认识 ChatGPT 的能力和限制
在经历过反复被折磨后,我决定放弃让 ChatGPT 写题解的想法,原因是:
其实,目前爆火的两类 AI 技术,都是工具型 AI,而不是科幻作品中常见的通用型 AI。无论是基于大语言模型 LLM 的 ChatGPT,还是基于扩散算法 Diffusion 的 MidJourney 等绘画工具,本质上都是使用海量数据针对特定场景训练出的模型。它们拥有让全人类难望尘莫及的数据处理能力,但它的能力上限也被封印在这个躯壳中。
AI 这个词,被泛化了。
像 ChatGPT 就是使用互联网上海量的文本数据作为大型预训练语料库,让机器从语料库中学习语言模型,并通过 Transformer 模型来预测下一个单词的概率分布。这里面有 2 个关键词:
图片截图自 InstructGPT 论文 https://arxiv.org/pdf/2203.02155.pdf
2.6 在学习过程中使用 ChatGPT 的指导原则
话说回来,我们给 ChatGPT 一个很高的期待值,然后以它达不到期待值为由否定它,是客观的吗?不是。
刚开始使用 ChatGPT 的时候,它所表现的能力的确让我们很多人感觉非常惊叹。只是随着实践使用次数增多,随着提问问题的深入和复杂化,ChatGPT 的错误才开始逐渐显现出来,直到最后令人失望而已,是我们把它捧得太高了!
所以,在使用 ChatGPT 等 AI 技术时,我们应该遵循哪些基本原则呢:
- 原则 1 - 主动降低预期:
我认为将 ChatGPT 称为人类有史以来最智能的一本字典,或许是正确的定位。
在搜索引擎时代,信息以数据的形式存储在全世界的数据库中,我们要从这些信息中获得解决方案,就需要花费大量的时间去触达、筛选、阅读和加工。而在使用 ChatGPT 后,ChatGPT 能够帮我们搜索和整理信息,并直接呈现出整理后的信息,即使我们对这个领域一无所知。
ChatGPT 最大的意义,在于它能够解决 “信息量太大 ”而 “注意力太少” 的矛盾。它能够在一瞬间将每个人在木桶效应中最短的那根短板提高到平均水平,每个人的延展性将被极大地延伸。
因此,虽然目前 AI 无法有效解决综合性的复杂问题,但对于比较基本的模式化的问题,ChatGPT 能在短时间生成完整的方案,也是相当厉害的。把对 ChatGPT 的预期降低,理解它的能力和限制,才能更好的控制它。
- 原则 2 - 监督和指导:
不要期望 ChatGPT 能够自主解决问题,更不用担心 AI 会取代人类。
ChatGPT 的确可以协助我们完成某些特定任务 / 特定动作,但它更需要在人类的监督和指导下才能有效产出,更无法代替人类的创造力和解决问题的思考力。
还有,大家都遇到过 ChatGPT 遇到复杂问题就开始一本正经地胡说八道,此时需要人类去主动查验和指导它直到给出正确答案。有时候,与其花时间调教 ChatGPT,还不如自己花时间一五一十解决问题来得快。
- 原则 3 - 思考的权利:
将思考的权利让渡给 AI,是危险的。
ChatGPT 确实具有总结的能力,但是过渡依赖于 AI 来辅助学习,放弃思考的过程,放弃探索未知的过程,放弃折磨大脑皮层的过程,是危险的。长此以往势必会造成学习能力和主观能动性的退化,抗压能力的退化(窥视真理,得到的就一定是真理吗)。
很多时候,我们追求的不仅仅是最终的答案,还包括寻求答案的过程,是一种所谓的 “心流” 状态。因此,越是折磨大脑皮层的动作,我们越不能让渡给 AI,而那些需要花费大量时间的重复的搜索整理动作,不交给 AI 还交给谁呢?
阶段 | 结论 |
---|---|
1、白板编码 | 100% 不暴露给 AI |
2、阅读优质题解 | 理解过程尽量不暴露给 AI,使用 AI 作为 Checker |
3、抽象问题模型 | 使用 AI 查找和整理信息 |
至此,我们结论确定。
接下来基于此结论开始使用 ChatGPT 辅助学习过程中的单个动作:
3. 面向 ChatGPT 编程的正确打开方式
3.1 使用 ChatGPT 输出提示
在做题时,有时候一下子卡住了但是又想挑战自己,可以让 ChatGPT 输出提示试试看,类似于 LeetCode 上的 Hint 功能。
我需要您担任算法教师和指导者,给出一道 LeetCode 问题的 5 条提示。
要求:
1、用中文回答,回答精简,限制在 15 字内
2、如果存在多种解法,优先提示复杂度最差的暴力解法,再依次提示复杂度更优的解法
问题:718. 最长重复子数组
这次 AI 给出了 5 个解法,而在之前的回答只给出了 2 个解法。
3.2 使用 ChatGPT 补齐题解结构
我需要你担任算法教师和指导者,你需要补齐一道 LeetCode 题的题解。
要求:
1、使用中文回答;
2、为代码增加注释
3、分析时间和空间复杂度
4、解释这个算法
代码:
class Solution {
public:
int collectTheCoins(vector<int>& coins, vector<vector<int>>& edges) {
int n = coins.size();
unordered_set<int> nes[n];
for(const auto& e : edges) {
nes[e[0]].insert(e[1]);
nes[e[1]].insert(e[0]);
}
// 1. 删除所有的无金币的叶子节点,直到树中所有的叶子节点都是有金币的(类似拓扑排序)
vector<int> deleted(n, 0);
queue<int> q;
for(int i = 0; i < n; ++i)
if(nes[i].size() == 1 && coins[i] == 0)
q.push(i);
while(q.size()) {
int cur = q.front(); q.pop();
deleted[cur] = 1;
for(int ne : nes[cur]) {
nes[ne].erase(cur);
if(coins[ne] == 0 && nes[ne].size() == 1) {
q.push(ne);
}
}
}
// 2. 删除树中所有叶子节点,及其相邻边(删两次)
for(int iter = 0; iter < 2; ++iter) {
for(int i = 0; i < n; ++i) {
if(!deleted[i] && nes[i].size() == 1) {
deleted[i] = 1;
}
}
for(int i = 0; i < n; ++i) {
if(deleted[i]) {
for(int ne : nes[i]) {
nes[ne].erase(i);
}
}
}
}
// 3. 答案就是剩下的树中的边数
int res = 0;
for(const auto& e : edges)
if(!deleted[e[0]] && !deleted[e[1]])
res += 2;
return res;
}
};
GPT-4 实验结果:https://poe.com/s/VIkeeiqjDGVfuym0Kn5B
3.3 使用 ChatGPT 辅助阅读
有些题解结果完整,讲解也很详细,但是其中某些难点或某几行代码过于复杂,可以针对性提出问题,ChatGPT 也有能力结合上下文回答。
3.3 使用 ChatGPT 翻译代码
我需要你担任算法教师和指导者,你需要将代码翻译为 Python:
要求:
1、包含代码注释
代码:
class Solution {
fun collectTheCoins(coins: IntArray, edges: Array<IntArray>): Int {
val n = coins.size
// 入度表
val inDegrees = IntArray(n)
// 领接表
val graph = HashMap<Int, MutableList<Int>>()
for (edge in edges) {
graph.getOrPut(edge[0]) { LinkedList<Int>() }.add(edge[1])
graph.getOrPut(edge[1]) { LinkedList<Int>() }.add(edge[0])
inDegrees[edge[0]]++
inDegrees[edge[1]]++
}
// 剩余的边
var left_edge = edges.size // n - 1
// 1、拓扑排序剪枝无金币子树
val queue = LinkedList<Int>()
for (node in 0 until n) {
// 题目是无向图,所以叶子结点的入度也是 1
if (inDegrees[node] == 1 && coins[node] == 0) {
queue.offer(node)
}
}
while (!queue.isEmpty()) {
// 删除叶子结点
val node = queue.poll()
left_edge -= 1
// 修改相邻节点
for (edge in graph[node]!!) {
if (--inDegrees[edge] == 1 && coins[edge] == 0) queue.offer(edge)
}
}
// 2、拓扑排序剪枝与叶子结点距离不大于 2 的节点(裁剪 2 层)
// 叶子节点
for (node in 0 until n) {
if (inDegrees[node] == 1 && coins[node] == 1) {
queue.offer(node)
}
}
for (node in queue) {
// 2.1 删除叶子结点
left_edge -= 1
// 2.2 删除到叶子结点距离为 1 的节点
for (edge in graph[node]!!) {
if (--inDegrees[edge] == 1) left_edge -= 1
}
}
// println(inDegrees.joinToString())
// coins=[0,0],edges=[[0,1]] 会减去所有节点导致出现负数
return Math.max(left_edge * 2, 0)
}
}
ChatGPT 会根据原有代码逻辑转层翻译为目标语言,基本完成得不错。美中不足的是,我从来没遇到过代码一次转换后不出错的情况,需要反复指导才能改对,实际作用一般。
3.4 使用 ChatGPT 规范代码
我们可以将自己写的代码交给 ChatGPT 做 CodeReview:
我需要你担任算法教师和指导者,你需要对代码做 CodeReview:
要求:
1、评价代码规范性
2、指出不足指出
3、给出改进后的版本
代码:略
图片过大,只截取部分信息。
大部分是一本正经地胡说八道,效果非常一般,跟市面上已有的 Review 工具完全比不了,果然 CodeReview 是个经验活 🌚!!
GPT-4 实验结果:https://poe.com/s/XelhIjmHxWFtbnZpLmEd
3.5 使用 ChatGPT 推荐相似题目
我们可以让 ChatGPT 举出相似题型:
我需要你担任算法教师和指导者,你需要给出 10 道同类型题目和链接:
题目:
718. 最长重复子数组
可以看到,ChatGPT 的学习深度只能分析到 “动态规划” 这一层,推荐的题目实际关联度不高。
我们尝试增加限定条件,例如要求算法模型都包含动态规划、滑动窗口和二分查找:
我需要你担任算法教师和指导者,你需要给出 10 道同类型题目和链接:
要求:
1、都包含动态规划、滑动窗口和二分查找解法
题目:
718. 最长重复子数组
这次相似度很高,不错。
是不是逐渐掌握调教 ChatGPT 的正确方式?更多 case 就不再展示了,希望这篇文章能够给你带来一些灵感或者思路。
4. 总结
回到文章开头的新闻。新闻里提到的 Google L3 级别,其实面向的是实习生或应届生的测试职位,考察的算法也是比较基础和模式化的问题,更不会涉及复杂的系统设计问题(但薪水也有 18.3 万美元,果然宇宙的尽头是外企)。
让我意外的是,在 OpenAI 的 GPT-4 技术报告中,GPT-4 在 LeetCode 算法上的测试数据表现非常糟糕。如果以 GPT-4 的水平是不可能通过 Google 的算法面试的,更不用说该新闻发表日期时的 ChatGPT 应该还在用 GPT-3.5 模型。
这个新闻的可信度很低。
GPT-4 技术报告:https://cdn.openai.com/papers/gpt-4.pdf
总结一下:
最后,希望大家在学习算法的道路上共勉,小彭等拿到 Guardian 牌子后来回来还愿 😭。
你认为 ChatGPT 技术是否被过度炒作?你对这个问题有什么见解?欢迎聊聊你的看,也欢迎你转发、留言、在看,给我一个反馈喔。
共同学习,写下你的评论
评论加载中...
作者其他优质文章