ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

贝壳找房C++笔试复盘:算法与底层机制全拆解

贝壳找房C++笔试复盘:算法与底层机制全拆解 我去年帮一个学弟复盘贝壳找房的春招C笔试试卷他在牛客上对照着答案一点一点回忆我负责给他拆解题思路。那套卷子整体给我的感觉是算法题不偏不怪但很吃基本功C语言题则明显在考察你是不是真的理解底层机制而不是背了一堆八股就完事。更关键的是这套试卷暴露了不少应届生在“会写代码”和“能过笔试”之间的差距——很多人败在了读题不仔细、边界条件没想清楚、复杂度分析不到位这些地方。这篇文章就把我对这份试卷的完整复盘整理出来从试卷结构、重点题型的解题链路到C语言特性考点的深度拆解再到一套适合大多数人的备考路径。不管你是准备投贝壳还是想摸清大厂C岗位的考察套路这份复盘应该都能帮上忙。1. 试卷结构拆解贝壳这套题到底在考什么能力先说结论整张卷子把80%的权重押在了算法与数据结构上剩下20%用来检验你对C语言本身的理解深度。题型分布大致是3道算法编程题、10道左右的语言/基础选择题外加1道现场手写代码的工程场景题。限时120分钟题量不算大但每道题都需要你在规定时间内写出效率达标的解法这就很考验临场状态。贝壳作为房产交易平台起家的公司技术栈里C主要服务的是搜索、推荐、交易链路这类对性能和延迟敏感的系统。所以笔试卷的算法题往往带一点“业务场景包装”但底层考察的还是经典数据结构。1.1 高频考点分布从热词反向推断出题偏好从近两年考生回忆的热搜关键词来看贝壳的C试卷反复出现的高频考点包括基础排序与变体冒泡排序、选择排序的优化写法以及“第K大/最小”这类排序衍生题。二分思想快速幂、二分查找边界问题、在有序数组里找目标区间。线性结构单调栈解决“下一个更大元素”类问题、链表反转与合并、字符串与数组的相互转换。数论入门最小公倍数、最大公约数、质因数分解等通常作为编程题的某个子步骤出现。C语言特性设计模式、constexpr的版本演进、多线程同步、回调函数、内存管理、string库底层。值得注意的一个细节是热搜词里“vscode配置C/C环境”的出现频率非常高这说明很多应届生平时刷题用的IDE是Visual Studio Code而不是Visual Studio或CLion。这本身没问题但笔试环境往往是牛客或赛码网自带的在线编辑器默认不开自动补全和语法检查平时过度依赖IDE提示的同学在这种环境下会明显吃慢半拍。我这个学弟就是吃了这个亏第一道题光写头文件和函数签名就花了五分钟。1.2 贝壳笔试的时间分配策略120分钟分配建议大概是算法编程题每题25~30分钟选择题每题1~2分钟工程场景题留30分钟。我见过不少人的失败模式是在前两道算法题上死磕争取满分结果第三道题没时间做工程题更是直接交白卷。实际上大厂笔试判卷通常不只看通过率每题只要通过部分测试用例就能拿到相应比例的分值所以合理的策略是“先保底、再冲满分”而不是跟一道题死磕到底。如果是平时刷题量在200题上下的同学这套卷子的整体难度我给个7分左右满分10分。比字节、腾讯的卷子温和一些但比中小厂明显更有区分度——它区分的主要不是“你会不会这道题”而是“你在紧张状态下能否把会的东西稳定输出”。2. 三道算法编程题的解题链路与踩坑复盘算法题是整张卷子的重头戏。下面的复盘不是原题复述而是把考点内核、完整推导过程、以及我让学弟反复练习后总结的坑点一并讲清楚。2.1 快速幂取模高频送分题但错误率惊人贝壳这套卷子的第一道编程题是快速幂的变体给定底数a、指数b和模数m计算a的b次方对m取模的结果其中b最大可以到10的18次方。这是一个典型的“送分题”因为解法非常固定——用二进制拆分指数把幂运算的复杂度从O(b)降到O(log b)。但实际考试中错误率并不低主要原因有三个第一很多人不知道要先取模再乘。直接算a的b次方在b较大时连long long都撑不住溢出是必然的。正确的做法是每一步乘法运算都对m取模保证中间结果始终在可表示范围内。第二指数二进制拆分的实现细节写错。标准写法是typedef long long ll; ll qpow(ll a, ll b, ll m) { ll res 1 % m; while (b 0) { if (b 1) { res res * a % m; } a a * a % m; b 1; } return res; }注意两个容易被忽略的细节res的初始值要写成1 % m而不是1因为当m等于1时任何数对1取模都是0这时如果初始化为1函数会错误地返回1另外a a * a % m必须在if判断之后执行顺序不能反。第三没考虑负指数或底数为0的情况。虽然题目约定指数非负但边界条件写得很含糊稳妥的做法是在函数开头加一个判断如果b为0直接返回1 % m如果a为0且b大于0直接返回0。我当时让学弟做这个题时给他的要求是手写一遍、编译运行、再闭卷手写一遍直到30秒内能无错误写完。快速幂在很多笔试卷里是作为前置工具函数出现的比如后面的组合数计算、斐波那契数列矩阵幂解法都可能用到它所以越熟练越有利。2.2 单调栈与“下一个更大元素”的变体场景包装下的裸数据结构题第二道题的原题描述比较长大意是说有一批房源价格列表要求找出每套房源右侧第一个价格更高房源的距离如果不存在则输出-1。剥掉业务外壳这其实就是一个标准的“下一个更大元素”问题最优解法是单调栈时间复杂度O(n)。单调栈的核心理念是维护一个栈栈中元素保持单调递减或递增当新元素打破这个单调性时说明栈顶元素的“下一个更大元素”就是当前新元素于是可以弹栈并记录答案。我当时让学弟把单调栈的模板背到条件反射的程度vectorint nextGreaterElement(vectorint nums) { int n nums.size(); vectorint res(n, -1); stackint st; for (int i 0; i n; i) { while (!st.empty() nums[i] nums[st.top()]) { res[st.top()] i - st.top(); st.pop(); } st.push(i); } return res; }这里有个非常容易错的细节栈里存的是下标而不是值。只存值的话你确实能判断“下一个更大元素是谁”但判断不了“距离是多少”而这道题恰好要求输出距离。很多人在这个细节上吃亏一提交就发现部分测试用例过不了。另一个容易忽略的极端情况是所有元素单调递减比如[5, 4, 3, 2, 1]。这种情况下任何元素都没有右侧的更大值栈会一直堆积最终所有结果都是-1。标准模板已经处理了这种情况但你得保证res初始化时就是-1否则结果会变成0占据了本该是-1的位置。单调栈这个考点在贝壳试卷里出现后又频繁出现在后续多家公司的笔试中。我建议刷题时把LeetCode 739每日温度和LeetCode 496下一个更大元素I做熟这两道题覆盖了单调栈的两种基本问法做透了之后应对变体就比较轻松了。2.3 最小公倍数与数组处理考察数论基础与细心程度第三道编程题是求n个整数的最小公倍数。n最大是100每个数字在int范围内。题目本身并不难但它在C实现层面设置了好几个陷阱。首先要知道最小公倍数的求法两个数的最小公倍数等于两数之积除以两数的最大公约数即lcm(a, b) a / gcd(a, b) * b。注意这里必须先除后乘如果先乘再除a * b可能溢出。依次求多个数的最小公倍数时用迭代的方式先求前两个数的lcm再把结果与第三个数求lcm依此类推。累积结果可能会超过单个数的范围这时候要用long long存中间结果。这是这道题最阴险的地方——题目没说结果有多大但几个int范围内的数求lcm后轻松突破long long的取值下限。C标准库中std::gcd在numeric头文件中从C17开始可用。笔试环境如果标准设置为C14std::gcd是不可用的需要手写一个int gcd(int a, int b) { while (b ! 0) { int temp b; b a % b; a temp; } return a; }很多人的错误在于忘记处理0的情况。如果输入包含0那么所有数的最小公倍数按数学定义是0但业务场景中0通常意味着数据缺失到底按0处理还是跳过取决于题目描述。贝壳这道题在描述里没提0的存在但判题数据里显然有0的用例那些没处理0的提交直接挂在了隐藏用例上。读题不仔细导致的边界漏判比不会做更可惜。这是整套卷子我反复跟学弟强调的一句话。3. C语言特性考点看起来在考语法实际在考底层认知选择题和简答题部分贝壳明显聚焦在C的核心语言机制上。下面挑几个热度高、大家问得多的考点逐个拆解。3.1 ABA问题的本质可能不是你理解的那个“本质”热词里的“ABA问题”是并发编程中的一个经典陷阱。它指的是在多线程环境下线程A读取共享变量的值为A然后被挂起线程B将值改为B再改回A线程A恢复执行后用CASCompare And Swap判断值与期望值A相同于是执行更新操作。但此时变量的值虽然还是A它的语义状态已经完全不同了——比如链表头节点的地址没变但头节点后面的节点已经被替换了。我用一个生活化的例子来解释你在多肉大棚挑中了一盆花去前台结账的工夫老板把这盆花搬去浇了点水又放回原位。你回来一看还是那盆花地址没变但里面的土和设备其实已经换过一轮了。ABA问题的核心不是“值相等”这个事实而是“值相等不代表状态未变”这个认知。在C笔试中考察点通常是让你指出ABA问题发生在什么场景、以及如何解决。标准答案有两个方向加版本号/标记这是最常用的做法。在每次CAS操作时不只比较值还比较一个单调递增的版本号。值相同但版本号不一致说明发生过修改。Java的AtomicStampedReference就是这种思路C可以用std::atomic配合计数器实现。使用不可变对象或避免CAS的滥用在某些场景下改为加锁或使用读写锁反而是更稳妥的方案。3.2 constexpr的版本演进从编译期常量到编译期函数这是热词里出现次数极高的一个考点“constexpr是哪个C版本引入的”。标准答案非常明确constexpr关键字是C11引入的但它在后续版本中经历了重大扩展。具体版本演进是这样的标准版本constexpr的变化C11引入constexpr只能用于声明简单的编译期常量或单条return语句的简单函数C14放宽限制constexpr函数体内可以使用循环、条件判断、局部变量成为真正的编译期函数C17允许constexpr lambda表达式可以在编译期求值的场景中使用lambdaC20进一步放开constexpr函数中可以使用try-catch、动态内存分配等特性如果选择题问的是“哪个版本引入的”答案是C11但如果问“哪个版本让constexpr函数更像普通函数”答案就需要区分C14和C17。很多应届生只记住了C11丢失了后续版本扩展的细节这在面试追问环节是很容易被扣分的地方。实际工程中constexpr的价值在于把运行时开销移到编译期典型场景是计算斐波那契数列、查找表、位运算等固定逻辑constexpr int fibonacci(int n) { return (n 1) ? n : fibonacci(n - 1) fibonacci(n - 2); } constexpr int fib10 fibonacci(10); // 编译期计算出55注意C11的constexpr函数要求函数体必须是一条return语句所以上面的写法只能从C14开始使用。我见过不少人在简历里写“熟悉C11/14”但真考到这种细节时会露馅所以还是得认真过一遍标准演进。3.3 C设计模式单例模式是面试官最爱的“套话开头”贝壳的选择题里至少出现了两处设计模式相关内容。一处给出了一个小型类定义问它属于哪种设计模式另一处则是简答题要求说明单例模式的优缺点。C面试中最常被问到的设计模式按频率大概是单例、工厂、观察者、策略、模板方法。其中单例几乎是100%出现的而且面试官特别喜欢追问“线程安全的单例怎么写”——这背后隐含的考点是C11以后局部静态变量的线程安全初始化class Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } private: Singleton() default; Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; };这个写法从C11开始是线程安全的因为标准规定局部静态变量的初始化过程是线程安全的编译器通常会生成双重检查加锁的代码。如果笔试环境要求兼容C03就得手动加锁。大多数时候标准答案是“C11用局部静态变量C03用DCLP双重检查锁定加volatile”但说实话工程上现在很少有人会去写C03的单例了。设计模式这个东西学习时的重点不应该是背UML图而是理解它解决了什么问题。比如单例解决的是“全局唯一实例”的获取问题工厂模式解决的是“对象创建与使用解耦”的问题。理解了问题域你才能在不同场景下选择正确的模式笔试简答题也才能写出有说服力的答案。3.4 回调函数与多线程从基础语法到常见陷阱回调函数在C里最常见的实现方式是函数指针、std::function和lambda表达式。贝壳笔试出现了一道题给出一段代码问lambda表达式捕获列表的问题。给定下面的代码请问输出是什么#include iostream #include functional int main() { int x 10; auto f [x]() { return x 5; }; x 20; std::cout f() std::endl; return 0; }答案输出15。原因在于[x]是按值捕获捕获发生在lambda创建的那一刻之后外部变量x再怎么变化lambda内部持有的x副本都不会变。如果改成[x]输出就会变成25。这个知识点本身很简单但它在多线程场景下的变体会很坑人。比如在std::thread中启动一个lambda如果你按引用捕获了局部变量而线程还没执行完局部变量就已经销毁就会产生悬垂引用程序会表现出难以排查的随机崩溃。这是使用回调时必须警惕的经典陷阱。我当时给学弟的建议是多线程环境下优先按值捕获如果确实需要引用捕获务必保证被引用对象的生命周期覆盖到线程结束之后。3.5 结构体链表的C语法细节热词里有一个“c结构体链表基本语法”这道题通常出现在选择或填空题中考察的是C与C在结构体上的差异C语言中定义结构体变量时要写struct Node而C中可以省略struct关键字直接用Node。这个区别源于C将结构体视为类的一个特例struct是默认public访问权限的class。下面是一个典型的链表节点定义与使用示例#include iostream struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; int main() { ListNode* head new ListNode(1); head-next new ListNode(2); // 注意此处是 - 而不是 .因为head是指针 std::cout head-val std::endl; delete head-next; delete head; return 0; }如果笔试改错题里出现head-next写成head.next或者head-val写成head.val你要能立刻反应过来错在哪。另外如果你用了new动态分配内存记得在程序结束前释放虽然牛客判题系统对内存泄漏不敏感但这属于代码规范问题工程题考官会看。3.6 字符串数组初始化与转数组选择题最爱挖的坑字符串在C里有两套体系C风格字符串char数组和std::string。热词里的“c字符串数组初始化”和“c字符串转数组”正好对应这两个方向。C风格字符串的初始化很容易出问题char str1[] hello; // 正确数组大小自动推导为6包含\0 char str2[5] hello; // 错误字符串字面量有6个字符放不下 char str3[] {h, e, l, l, o}; // 长度为5没有\0它不是合法C字符串选择题里问“以下哪个初始化方式正确”时char* str hello这个选项是陷阱在C中字符串字面量是const char数组用char*指向它属于类型退化从C11开始会编译报错正确写法是const char* str hello。std::string转字符数组也是高频考点std::string s hello; const char* cstr s.c_str(); // 指向C风格字符串不能修改 std::vectorchar vec(s.begin(), s.end()); // 转成vector不含\0 char buf[6]; s.copy(buf, 5, 0); // 手动拷贝不会自动加\0 buf[5] \0; // 需要手动补上结束符总的来说字符串相关考点考的不是你会不会用std::string而是你是否清楚std::string和C风格字符串之间的边界——什么时候会隐式转换、什么时候需要手动调用c_str()、什么情况下指针会失效。4. 工程场景题一道暴露真实编码习惯的“手写题”这套卷子的最后一道题是工程场景题也是我最想让读者重视的部分。它的题面通常模拟一个真实业务场景比如“实现一个简单的内存缓存支持设置过期时间”或“设计一个线程安全的日志类”。这类题不要求完整实现一个工业级组件但要求你写出结构清晰、考虑边界、有注释的代码并且要处理并发安全、资源管理、错误处理等实际工程问题。4.1 典型题目还原实现一个线程安全的LRU缓存贝壳原题我不完全记得但考察套路和这道题非常类似。要求实现一个定长的LRU最近最少使用缓存支持get和put操作get和put的平均时间复杂度为O(1)。当缓存达到容量上限时删除最久未使用的键值对。这道题的经典解法是哈希表双向链表哈希表负责O(1)的键查找双向链表维护访问顺序。链表的头部是最近访问的节点尾部是最久未访问的节点。我先让学弟写了不考虑线程安全的版本跑通了再考虑加锁class LRUCache { private: struct Node { int key, value; Node* prev; Node* next; Node(int k, int v) : key(k), value(v), prev(nullptr), next(nullptr) {} }; std::unordered_mapint, Node* cache; Node* head; Node* tail; int capacity; void removeNode(Node* node) { node-prev-next node-next; node-next-prev node-prev; } void addToHead(Node* node) { node-prev head; node-next head-next; head-next-prev node; head-next node; } void moveToHead(Node* node) { removeNode(node); addToHead(node); } Node* removeTail() { Node* node tail-prev; removeNode(node); return node; } public: LRUCache(int cap) : capacity(cap) { head new Node(-1, -1); tail new Node(-1, -1); head-next tail; tail-prev head; } int get(int key) { if (cache.find(key) cache.end()) { return -1; } Node* node cache[key]; moveToHead(node); return node-value; } void put(int key, int value) { if (cache.find(key) ! cache.end()) { Node* node cache[key]; node-value value; moveToHead(node); return; } Node* newNode new Node(key, value); cache[key] newNode; addToHead(newNode); if ((int)cache.size() capacity) { Node* removed removeTail(); cache.erase(removed-key); delete removed; } } };4.2 手写LRU时的三个致命细节这个解法看似简单但笔试环境下手写时会有三个高频致命错误第一忘记处理双向链表的头尾虚拟节点。如果你不用虚拟头尾节点删除节点的操作会面临大量边界判断删除头节点怎么办、删除尾节点怎么办、链表只有一个节点怎么办。用虚拟节点后这些问题全部消失代码简洁且容错率高。我在纸面推演时让学弟先画图明确每个指针的指向再落笔写代码错误率能降低一半以上。第二get和put操作都需要做“访问更新”。get命中一个节点后这个节点变成最近使用的必须移动到链表头部。很多只背题解的人会漏掉get中的moveToHead操作。第三put重复键时需要更新值并移动节点而不是直接插入新节点。如果忽略已存在的键会生成重复节点导致缓存中同一key对应多个链表节点get时会命中错误节点。4.3 从单线程版到线程安全版加锁粒度很关键如果笔试有时间可以进一步展示你对并发安全的理解——给LRU加锁。但加锁的方式有讲究class ThreadSafeLRUCache { private: LRUCache lru; std::mutex mtx; public: int get(int key) { std::lock_guardstd::mutex lock(mtx); return lru.get(key); } void put(int key, int value) { std::lock_guardstd::mutex lock(mtx); lru.put(key, value); } };用最粗粒度的互斥锁把整个操作锁住是正确性优先的做法。如果在面试官面前表现出“我知道可以用读写锁优化读多写少的场景甚至可以用分片锁或无锁结构来提升并发度但工程上要从简单正确开始”的思考路径这个维度上的区分度就出来了。比起一上来就写读写锁然后出现并发bug先把锁加对、再谈优化更符合工程直觉。笔试中对这道题的常见评分标准是能做到功能正确拿基本分边界条件处理好拿部分加分考虑到并发安全会显著加分给出性能优化思路但不过度设计则属于印象分。多数人拿一半分并不难难的是把三道算法题和这道工程题都稳定拿到中上水平。5. 备考路径与实战心得三个月从“会写题”到“过笔试”前面考点拆解了很多但如果你的时间规划有问题再多的知识点也发挥不出来。以下是我结合贝壳和类似大厂C笔试总结的备考路径三个月的周期足够大多数人从勉强能做一部分题提升到稳定能做全大部分题。5.1 第一个月主攻算法模板与C语法细节第一个月的重心应该放在掌握高频算法的“标准模板”上。快速幂、单调栈、前缀和、差分数组、二分查找边界、拓扑排序、Dijkstra、并查集、Trie树、字符串匹配KMP这些是C笔试卷的常客。每一类算法都要整理出一个自己的模板并且用你日常使用的编辑器反复手敲到肌肉记忆。这里强烈建议你在本地的VSCode里把C/C环境配置好因为牛客或赛码网的在线编辑器没有智能提示你必须习惯“写代码靠脑补而不是自动补全”的状态。VSCode里配置C环境核心步骤就是安装C/C扩展、配置好MinGW-w64编译器、设置好tasks.json和launch.json然后用一个简单的Hello World验证环境没问题。与此同时C语法细节要每天过一遍constexpr版本演进、智能指针的使用场景、移动语义与右值引用、std::function和lambda的捕获方式、虚函数与多态的底层原理、内存对齐与内存布局。这些不只是选择题考点更是面试八股的常青树。5.2 第二个月刷真题与限时模拟建立答题节奏第二个月开始用牛客网或赛码网刷大厂真题尽量还原笔试环境设置倒计时、全程不切出去查资料、代码写完就提交检测。每周至少做两套完整的模拟卷且每套都按下面的节奏执行先做编程题再做选择题最后做工程题一道题超过40分钟没思路就立刻跳过。为什么强调先做编程题因为编程题分数占比高且需要清醒的头脑和手熟的手感。把最难的编程题放在脑子最清醒的时间段解决选择题这种“快速拿分但需要细心”的内容放在中间工程题放在最后——它需要的是代码结构能力对时间敏感度稍低。每次模拟结束后写一份复盘笔记。我的习惯是用表格记录每道题的错误类型题目错误类型具体原因修正方案快速幂溢出忘记每一步取模熟练写模板先取模再乘单调栈逻辑错误栈中存值未存下标记清模板明确存下标字符串转数组边界忘记追加\0检查字符串操作的结尾符通过这种方式你会发现自己的错误高度集中在几个类型上溢出、边界、漏case、读题不仔细。针对性地修正比盲目刷题有效得多。5.3 第三个月八股强化与工程思维定型第三个月重点是C八股的系统整理和工程思维的建立。热词里出现的“c八股文”“c八股”“c面试题”说明这是大家普遍关注的方向。我建议把八股按主题分成块内存管理、编译链接、并发编程、设计模式、STL源码原理。每个主题下准备2~3个“故事”你在什么场景下遇到了什么问题你是如何定位和解决的底层原理是什么。同时工程题的练习不能只写单线程版本。花一周时间把基础的线程安全组件过一遍单例模式线程安全版、生产者消费者队列、带过期时间的缓存、线程池的简化实现。这些组件是后面很多公司笔试工程题的“拼图材料”写熟之后看到工程题就能快速拆解成这些模块的组合。5.4 我第一次做这套卷子时的踩坑回忆说点个人经验。我第一次做这套贝壳试卷的模拟版本时三道算法题里有两道在本地IDE里秒过但放到牛客在线编辑器后出现了诡异的段错误。排查半天发现是因为我在本地用的编译器版本支持C17的std::gcd而在线环境是C14标准库根本没有这个函数。那之后我养成了一个习惯在本地刷题时默认把编译标准设置为C14而不是最新版本。这样可以强制自己不依赖新版本的语法糖和标准库函数真到了笔试环境反而能写出兼容性更好的代码。很多同学平时用C17甚至C20的特性写题很爽到了笔试环境突然不支持就只能干瞪眼。还有就是平时练习时给自己一个要求写代码不开自动补全函数名和头文件全凭记忆敲出来。这样练上两周笔试时在线编辑器的输入延迟、无补全、无语法高亮都不会对心态造成太大影响代码照样能流畅输出。别小看这个细节身边太多因为不适应在线编辑器而发挥失常的人了。6. 写在最后比刷题更重要的复盘思维贝壳这套卷子复盘到这儿整体结论很清晰它的题目量不大考点也都在主流范围内但区分度极高——因为每一道题都在测试你“是否真的懂”而不是“是否背过”。快速的幂背下来就能得分但会不会处理1 % m这一行单调栈模板背下来就能得分但会不会在栈里存下标而不是值LRU缓存背下来就能得分但会不会处理get时的moveToHead——这些细节才是真正拉开差距的地方。我更想说的是备考C笔试时别只埋头刷题每做完一套题花30分钟复盘“我为什么错”、把错误归类、把薄弱点整理成表格这套工序的长期价值远超多刷十道新题。我的学弟最后是这么做的他把错题按类型整理成自己的“避坑手册”考前的最后一周只看这本手册而不是继续刷题最终顺利通过了贝壳和其他几家公司的笔试阶段。如果你也在准备C方向的春招或秋招不妨找一套真题严格限时做完然后像我这样逐题复盘一遍。笔试的本质不是考你认识多少种解法而是考你在有限时间内能不能稳定地写出正确的代码。这种能力没有捷径只有靠一次次的模拟、纠错、再模拟来打磨。希望这篇复盘能帮你少走一些弯路。
返回列表