上一篇的地址在这里,没有看过的同学建议先看第一篇 来和大家聊聊我是如何刷题的(第一弹)。
这次继续给大家聊聊怎么刷题, 预计分几篇文章来写,今天是第二篇,本系列至少会出三篇。这次分享的内容是代码书写技巧以及调试技巧。
本系列旨在分享一些所有题目都适用的技巧以及一些刷题经验,帮助大家高效刷题。如果想重点突破某一类题目,可以关注我的专题系列。
话不多说,直接上干货。
先刷什么?刷多少?
正式介绍技巧之前先回答一个问题,这也是我被问的比较多的两个问题是:我该先刷什么算法?每一种算法我该刷多少?
现在我们就来看下这个问题。
先贴一个 91 天学算法 中某一小节的讲义中的一部分内容:
- 递归(10)
- BFS & DFS(20)
- 双指针(20)
- 滑动窗口(6)
- 哈希表(20)
- 回溯(5)
- 动态规划(20)
- 排序(3)
- 分治(20)
- 堆(3)
- 贪心(5)
- 设计题(5)
- 图(5)
- 位运算(5)
- 并查集(3)
如果你不知道从何刷起,可以参考我的这个刷题顺序,其中括号是我推荐的最小刷题量,就是说再少不能少于这个数字。如果你想多刷,可以按照我的这个比例去刷。等到你自己有个概念,知道自己哪里薄弱了,再去针对加强即可。
具体题目太多了不列举了,给大家几个题目集合做参考:
- 🔥 热题 HOT 100
- 👨💻 精选 TOP 面试题
- 企业题库。 比如 🐧 腾讯精选练习 50 题,企业题库 - 字节跳动(当你就想去某一家公司的时候可以用)
- 剑指 Offer
- 网友内幕(主要是面经)
- 力扣的探索和标签
知道该刷什么,以及刷多少了,可能你已经迫不及待投入题海了。 不要着急,可以先看下西法有没有写过相关专题,如果写过, 强烈建议你先看下,一定能让你事半功倍。
我已经开始刷专题了,有没有什么通用的技巧呢?答案是有,而且很多。本文只介绍一部分, 后续我们继续这个话题,给大家带来更多干货技巧。
代码书写技巧
代码书写技巧,这次给大家带来三个技巧:
- 改参数
- zip 函数的妙用
- 关于取模
改参数
力扣的参数是可以改名字的,如下图:
你可以将名字改成一个短的或者你熟悉的。比如上面这道题,我写的时候就可以:
1 | class Solution: |
这可以使得代码看起来简洁且具有一致性。
经常看我题解的小伙伴应该注意到我的代码比较简洁。一方面是因为我经常用 Python,另一方面就是因为这个技巧。
这里我顺便吐槽一下力扣。力扣的形参命名相当不规范。比如二维数组有时候是 mat,有时候是 nums,有时候是 matrix,有时候又是 grid 。。。 真心不舒服,不过有了这个技巧,大家就不要依赖官方了,自己统一一下就好。
就拿我来说,二维数组我就用 mat 或者 matrix,一维数组用 nums 或者 A 或者 A 和 B(两个一维数组的情况)。比如:
1 | # A 和 B 是两个一维的数组 |
其实不仅仅是形参的命名要统一,我们内部的代码也是一样的。对于我来说:
- 堆我习惯叫 h
- 图我习惯叫 graph
- 队列我习惯叫 q
- 。。。
大家没有必要和我一样,但是一定要保持一致性,这样可以显著增加代码可读性,可读性高了,调试工作也会变得轻松。
zip 函数的妙用
力扣有一些题目会给你两个或者三个一维数组,这两个一维数组的是有关联的。
比如给你两个一维数组 A 和 B,其中 A[i] 表示第 i 个人的体重,B[i] 表示第 i 个人的身高。也就是说都是表示第 i 个人,但是表示的东西不一样。
其实逻辑上就相当于结构体,而且如下结构体的形式在工作中更常见。
1 | interface Person { |
但是力扣以两个数组的形式给你了,其实这样不难啊,不就是用一个索引记录么?
1 | for(int i= 0;i<A.length;i++) { |
但是如果我需要对重量排序呢?如果你仅仅对 A 排序了,B 也需要进行相应调整的,否则对应关系就乱了。那遇到这样的情况该怎么办呢?
这里介绍一个我经常使用的技巧 zip。
1 | zipped = zip(A, B) |
由于 A 和 B “捆绑”到一起了,因此排序也不会改变其相对顺序。
如下是我在力扣 1383 题中使用 zip 技巧的例子:
另外 zip 还有一些其他用处。比如我想要获取当前数组位置的前一项。
不用 zip 可以这么做:
1 | for i in range(1, len(A)): |
如果使用 zip 可以这样:
1 | for pre, cur in zip(A, A[1:]): |
这里的原理也很简单。我举个例子你就懂了。比如有一个数组 A :[1,2,3,4]。 那么 A[1:] 就是 [2,3,4]
我将如上两个数组 zip 起来就是 [[1,2], [2,3], [3,4]],所以我对 zip 之后的结果进行遍历就可以方便地写代码了。
这个技巧用处不大,可以不必掌握,大家知道有这么回事就行
有的人可能想问,我的语言没有 zip 怎么办? 我的答案是自行实现 zip。 比如 JavaScript 可以这样实现 zip:
1 | const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c])); |
你把它改造成自己的语言版本即可。
关于取模
力扣中有很多题目需要你对返回值取模,而且一般都是对 109 + 7 取模。
题目答案让取模那肯定是答案太大了,为啥太大了呢?有啥想法没?
题目描述:
1 | 给你一个整数 n ,请你将 1 到 n 的二进制表示连接起来,并返回连接结果对应的 十进制 数字对 109 + 7 取余的结果。 |
如果我忘记取模或者仅在返回的时候取模都可能会报错,正确的姿势是提前取模。
以上面的题目来说,代码这样写是可以过的。
1 | class Solution: |
而如果我这么写会超时(没有提前取模,只是在最后返回才取模):
1 | class Solution: |
如果不提前 mod, python 可能超时,其他语言可能溢出。
提前取 mod,会把数值限定在 int 能处理的范围,使用机器自身整数运算功能进行快速运算,而如果之后取 mod,由于 python 对大整数支持的特性,会将 ans 转换为大整数再进行运算,计算相对耗时。
说到溢出,我想起来一个小技巧,那就是二分取中间值的时候如果书写不当也可能溢出。
1 | # 如下代码,如果 l 和 r 比较大,则可能发生大数溢出 |
这里的 // 是地板除
解决的方案也很简单,这样写就行了:
1 | mid = (r - l) // 2 + l |
另外一个和取模有关的小技巧是判断奇偶。
判断一个数是奇数还是偶数可以通过和 2 取模。 如果返回值是 0 则是偶数,否则是奇数。
需要注意的是和 2 取模为 1 奇数,但是反之不然。即和 2 取模不是 1 也可能是奇数,比如负数,因此还需要多个判断,不如用我上面的方法,即和 2 取模判断是否等于 0。
欢迎大家补充其他小技巧~
其他技巧
这里有一个要和大家强调的点,很多刚刷题的人都不知道,那就是尽量不要使用全局变量。
如果使用全局变量且没有及时清除,不仅可能有性能问题,更可能会在多个测试用例之间形成干扰,导致出错。而且在力扣设计题目通常是会多次调用某一个 api 的。这个时候更是如此,所以不要使用全局变量。
有一些朋友向我反馈“为啥本地好好的,放到力扣上提交就不行”,请先检查下有没有使用全局变量。
调试技巧
调试技巧,我们这里先讲两个:
- 批量测试
- 数据结构可视化(树的可视化)
批量测试
力扣的测试用例其实是可以一次写多个的。
如上图,该题目有两个参数。那两行其实就是一个完整用例。 我这里输入了六行,也就是三个用例。这个时候点击执行,就可以一次执行三个用例。
妈妈再也不用担心我提交太频繁啦~
执行成功后,我们可以一次查看所有的差异。
如果你老是考虑不到各种边界,那这个功能简直是福音。 另外如果你打比赛,你可以把题目给的测试用例批量复制到这里一次执行看结果,非常有用。
为了方便大家复制所有题目内置的测试用例,我的刷题插件 leetcode-cheatsheet增加了一个功能一键复制所有的内置用例。
正常情况,下点击之后会提示你复制成功,你只需要 ctrl + v 粘贴到测试用例的输入框即可。
但是力扣网站有很多不是很统一的地方,这就需要我不断进行兼容。比如如下兼容代码:
上面代码指的是力扣测试用例的 html 标签是不定的,并且有时候是”输入:“(注意是中文的:),有时候又是”输入:“(注意是英文的:)。
因此难免有我无法兼容的情况。因此就会发生类似这样的情况
:
遇到这样的情况,你可以点击弹出信息的反馈文字,给我反馈。不过据我的测试,大部分情况是没问题的。
插件目前已经发布到谷歌商店了,通过谷歌商店安装的朋友审核通过后会自动更新。离线安装的朋友需要手动安装,不过我的更新蛮频繁的,强烈建议在线安装。商店地址:https://chrome.google.com/webstore/detail/leetcode-cheatsheet/fniccleejlofifaakbgppmbbcdfjonle?hl=en-US
上线几天已经有 100 多人安装了, 你确定不试试么?
树的可视化
力扣支持大家对树进行可视化,只要点击这个树结构可视化按钮即可(只有树的题目才有这个按钮)。
如果你写了多个数组,也并不会生成多个树,貌似是以最后一次输入为准。
力扣暂时没有提供其他数据结构的可视化,比如数组,链表等。这可能对大部分人来说没什么,但是对于我这样经常写题解,画图的人就不一样了。如果可以快速画图,那么对我效率肯定有大幅度的提升。
lucifer 建议大家也养成写题解的好习惯。
因此我打算在我的刷题插件里面加其他数据结构的可视化功能, 已经在规划啦~ 现在草稿了一些东西。
比如这样的树:
和这样的树:
现在其实还有些问题,而且我想多加几种数据结构方便写题解,所以就之后再说好了。
本地调试技巧
我们可以通过按照编辑器插件在本地编辑器中写代码,然后通过编辑器插件将其提交到力扣即可。
这样你在本地的调试插件都可以用于算法调试了。 这里推荐两个可视化调试插件:
推荐一个网站
OI Wiki 致力于成为一个免费开放且持续更新的 编程竞赛 (competitive programming) 知识整合站点,大家可以在这里获取与竞赛相关的、有趣又实用的知识。我们为大家准备了竞赛中的基础知识、常见题型、解题思路以及常用工具等内容,帮助大家更快速深入地学习编程竞赛中涉及到的知识。
其他
力扣提交成功之后除了可以看到自己的排名情况(击败百分之多少),还可以查看别人的提交代码。
这里可以看所有分段的分布情况,也可以直接点击对应的柱子,查看别人的代码怎么写的。比如我这里直接点开了击败 100% 的代码,研究下 ta 是怎么写的。
发现确实代码比我的好,于是我就又”学会“了一招(技能++)。
以上就是本文的全部内容了。大家对此有何看法,欢迎给我留言,我有时间都会一一查看回答。更多算法套路可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 38K star 啦。大家也可以关注我的公众号《力扣加加》带你啃下算法这块硬骨头。
预告
下期给大家讲更加干货的技巧,一定不要错过哦。
- 一看就会,一写就废, 如何克服?
- 如何锁定使用哪种算法。比如我看到了这道题,我怎么知道该用什么解法呢?二分?动态规划?