我的LeetCode刷题初体验
背景
2019年08月03很幸运的参加了第一期 《极客大学前端训练营》,成为了winner大神的座下弟子,老师的第一堂课,就让我们自己写出一个黑白棋项目,第一堂课就被虐的很惨,因为黑白棋的吃子逻辑和落子逻辑,理解起来并不难,但是如何用编程语言去将这些逻辑转换成一行一行的代码,我发现自己竟然除了基础的 for if else 之外其余的都不知道怎么去写,最后看着老师逐行写出来之后,看到这些 while for if else 竟然可以这么奇妙的组合在一起来实现这套复杂的吃子、落子、边界等等的判断,而且代码简洁易读,自己除了惊呆还是惊呆,自己默默在心理幻想了一下:这要是我当着一群师妹的面,从头至尾敲出来一套可以跑的黑白棋代码,是件多么帅的事啊!
原因
我们平时主要都是面向业务进行编程,而复杂的业务最终都会被拆分为各个简单的子项问题,每个子项问题公司内部已有封装后的函数或者UI样式库,来服用解决此类问题,我们平时做的就是将新业务拆分子问题,然后去公司的wiki里面寻找已实现的api并复用,很少自己去写一套复杂逻辑(复杂的都是人家架构师或者公司大佬们去写),所以都没刻意练习过,写不出黑白棋逻辑很正常,能力上的缺陷越早发现就意味着有更多的时间和机会去解决这个缺陷。
“编程能力就是将实际业务逻辑转换为对应编程语言规则下的可运行代码能力”。 ——winner
解决方案
在winner老师的指导下,在我们班长、各组组长的共同组织下,我们成立了刷题小组 LeetCode题库,设定群规每人每周最低刷三道题,刷不够题数的要做一次组内分享,在组织的带领下我们各个成员按照自己感兴趣的标签都开始刷了起来。
刷题之旅的开始
1.三分钟热血
我审了一下leetcode网站,发现标签为简单的题目我都写不出代码,我感到很焦虑,我想起上学的时候,数学老师说过: “千古文章一大抄,不会写作文你就死记硬背下来优秀文章,然后你就会写了。” 想想平时工作中也经常遇到难题,产品小姐姐就差把刀驾到我的脖子上催促着快点实现需求,根本不会有太多时间让自己去试错,唯一的方式是搜索已有的可行解,然后复制粘贴到自己的工程中debug后,使用搜索的代码去解决问题。
于是我开始主动看题解,然后根据题目的意思去理解题解中的每一步代码的含义,理解后在去刷类似的题目,尝试自己用刚才 “理解”后的方式去写出类型题的答案,然后去leetcode测试和调试代码的bug,最终实现出来的代码运行效率都差强人意,对比那些好的题解中的代码后,意识到自己的使用数据结构和逻辑判断部分优化的空间很大,然后对照着修改自己的代码,最终在胜率达到 60% 以上,再去刷另一道题。
2.首战告捷
刷题的第一周,组内同事大部分都出去团建了,剩下几个人留守,除了处理常规的线上问题之外,工作压力不大,所以我有大把的机会将部分时间投入到刷题的乐趣中,每刷一道题都感觉像打游戏过了一关,这样我第一周的战绩 29 道,成为我们组内刷题战绩榜首。
3.刷题后的思考
- 小伙伴都问,你一周刷29道是因为你技术能力很强吗?
- 其实我的能力一直都比较水,我是我们组内打杂垫底的技术小白,我刷29道是因为当时工作任务不太忙,还有重要的事情说三遍,俺当时刷的都是各个标签里面的 简单题 简单题 简单题 ,而且这些标签都是【字符串】、【数组】、【哈希表】、【数学】等标签,题目类型平时工作中遇到几率多了些,稍微有点思路就多做了几道,所以题数看着多了点
- 刷题多真的会提升编程能力吗?
- 根据我的刷题经历,只求数量不求质量对编码能力的提升度并不高,因为机械的记忆是暂时的,真正的记忆是理解之后的记忆,其实就是没有形成脑图,后来在winner老师的指导下,开始尝试通过刻意练习的方式,来重新刷那些已刷题目,然而再次重刷那些题目,我还是背着写不出来,这就 尴尬了!于是我决定 喝瓶啤酒 冷静一下,其实努力没有错,错误的是没有正确方法论的指导下的盲目努力,于是我又重新明确目标,【不求量,只求能真正理解】,即:自己不看题解的情况下也能写出一道题目的解题代码,刚开始的时候是枯燥的,但随着复写次数多了,就渐渐发现,有些题目其实 伪代码 的样板是类似的,只不过有些分支判断逻辑存在差异,尝到开悟后的喜悦,我就开始不厌其烦的重复刷一道题,每道题最少刷15次,直到自己能默写下来为止
- 不能白参加训练营一回
- 用一句话说:哥们好歹也是前端训练营出来的,能否将学到的技能用在刷题上呢,我看题解中那些图片动态的解析代码,挺好的,但是还是感觉不够直观,要是把每一步代码的执行情况,都用页面元素展示出来不是更加直观吗,能更进一步加深对题目代码的理解,于是自己就尝试着将代码逻辑,简单的可视化
- 例如: 76题-最小覆盖子串的题解
- 示例代码 github链接
let minWindow = (s, t) => {
if (!s || !t || s.length < t.length) return "";
let [tCount, winCount] = [new Map(), new Map()];
// 统计字符串 t 中每个字母出现的次数
for (let ct of t) tCount.set(ct, (tCount.get(ct) || 0) + 1);
let [left, right, match, start, minLen] = [0, 0, 0, 0, s.length + 1];
let showDiv = null;
while (right < s.length) {
if (match < t.length) {
let rightKey = s.charAt(right);
if (tCount.has(rightKey)) {
winCount.set(rightKey, (winCount.get(rightKey) || 0) + 1);
if (winCount.get(rightKey) <= tCount.get(rightKey)) {
match++;
}
}
mountText("左指针固定,移动右指针!");
showDiv = mountChild();
renderChildren(showDiv, left, right, "2px solid green");
mountArrow(left, right - 1, s);
right++;
}
while (match === t.length) {
if (right - left < minLen) {
mountText(`找到和已有可行解 "${s.substr(start, minLen)}" 比较后的最小可行解:【${s.substr(left, right - left)}】!`, 'blue');
showDiv = mountChild();
renderChildren(showDiv, left, right, "2px solid green");
mountArrow(left, right - 1, s);
start = left;
minLen = right - left;
} else {
mountText(`窗口内字符 "${s.substring(left, right + left - 1)}" 比现有可行解【${s.substr(start, minLen)}】长度大或相等,` + "右指针固定,继续移动左指针!");
showDiv = mountChild();
renderChildren(showDiv, left, right, "2px solid green");
mountArrow(left, right - 1, s);
}
let leftKey = s.charAt(left);
if (tCount.has(leftKey)) {
winCount.set(leftKey, winCount.get(leftKey) - 1);
if (winCount.get(leftKey) < tCount.get(leftKey)) {
match--;
}
}
left++;
if (match < t.length) {
mountText("窗口内元素种类或个数不符合预期,固定左指针,开始移动右指针!");
showDiv = mountChild();
renderChildren(showDiv, left, right, "2px solid red");
mountArrow(left, right - 1, s);
}
}
}
mountText(`右指针已到达边界,循环结束,最小覆盖子串为:"${s.substr(start, minLen)}"!`, 'blue');
return minLen === s.length + 1 ? '' : s.substr(start, minLen);
};
-
贵在坚持
- 只有不断通过刻意训练,才能逐步让自己悟道一些问题的重复性,然后寻找到通用的解题方案,继而将碎片化的知识点整合为树状脑图,真正的做到了提升编程能力!
发明是百分之一的聪明加百分之九十九的勤奋。 ——爱迪生
总结
刷题没有捷径可以走,只有不断的刻意练习,并且真正体会到了那种 “开悟” 那种感觉,才会越刷越快,每个人的闲暇时间数量或知识体系都会影响速度,但是只要努力就一定会到达终点,小伙伴们,一起加油!
定个小目标,赚它一个亿。 ——王健林