大家好,我是吴师兄。
在算法学习的道路上,刷题是一个不可或缺的过程。作为程序员,无论是准备面试还是提升自己的编程能力,LeetCode 等平台上的算法题都成为了必修课。然而,很多人在刷题的过程中会感到迷茫,不知道该从何入手,甚至会陷入刷题无效的困境。
今天,我想分享一些个人的刷题经验,帮助大家更高效地掌握算法题目的解题技巧和方法。
一、合理的刷题顺序
刷题的核心之一在于建立一个合理的刷题顺序,而不是简单地按照 LeetCode 上的题号顺序来进行题目解答。
LeetCode 题目的编号虽然看似有序,但并没有按照学习难度或知识点进行合理的编排。很多初学者容易犯的一个错误就是从编号1开始刷,但实际上,编号1的“
两数之和
”这道题看似简单,却涉及了
哈希表
这一数据结构。如果没有对哈希表的了解和掌握,这题可能会卡住很多刚刚接触算法的同学。
再比如编号4的“
寻找两个正序数组的中位数
”,这道题目涉及复杂的二分查找和归并的思想,大多数人如果没有做过至少100道算法题,这道题几乎是不可能在合理时间内解出来的。
如何制定合理的刷题顺序?
-
从简单到复杂,按知识点递进刷题
:建议先从数据结构的基础知识开始,循序渐进地学习和刷题。比如从数组、链表、栈、队列等数据结构入手,逐渐过渡到树、图等高级数据结构。这样做的好处是可以系统地掌握基础数据结构,并且随着学习的深入,对这些知识的应用会更加熟练。
-
划分题目类型,集中解决一类问题
:刷题的过程中,我们可以将题目按照类型进行划分,比如动态规划、回溯、贪心算法等。针对每一种类型题目,可以先学习相关的理论知识,然后集中攻克一类题目。这样的刷题方式可以帮助你在短时间内掌握一类算法的精髓,避免知识点混杂在一起,难以巩固。
-
利用经典题目作为突破口
:LeetCode 上有一些题目被认为是“经典题”,因为它们能够很好地概括某种算法或数据结构的核心思想。这些题目往往会出现在面试中,也是学习某种算法的绝佳入门题。比如对于
动态规划
,经典题目如 LeetCode 70“
爬楼梯
”、LeetCode 322“
零钱兑换
”等是非常好的入门题目。通过攻克这些题目,你会对动态规划有更深刻的理解。
刷题顺序推荐:
-
数组与链表
:这是最基础的部分,掌握这些结构的操作对于接下来的学习至关重要。建议从 LeetCode 1“
两数之和
”、LeetCode 206“
反转链表
”、LeetCode 283“
移动零
”等题目入手。
-
栈与队列
:在熟悉了线性数据结构后,开始深入学习栈与队列。这类结构在解决括号匹配、递归等问题时非常重要。经典题目包括 LeetCode 20“
有效的括号
”、LeetCode 155“
最小栈
”。
-
树与图
:树结构是许多复杂算法的基础,二叉树、二叉搜索树、图等是高级数据结构的核心内容,建议从 LeetCode 94“
二叉树的中序遍历
”、LeetCode 226“
翻转二叉树
”等开始。
-
动态规划
:这是算法中的难点,也是最容易卡住的部分。经典的动态规划题目如 LeetCode 70“
爬楼梯
”、LeetCode 53“
最大子序和
”等都值得反复练习。
-
回溯与贪心
:这些策略在解决组合问题时非常有效,回溯算法可以帮助解决排列组合问题,如 LeetCode 46“
全排列
”。贪心算法适合解决局部最优问题,如 LeetCode 55“
跳跃游戏
”。
二、刷题的方法
仅仅有一个合理的刷题顺序还不够,刷题的方法同样重要。
刷题的过程类似于我们学习数学的过程,都需要经过“
概念学习
、
例题理解
、
实际练习
”这三个步骤。
很多人喜欢直接跳到第三步,也就是拿到题目就直接开始解题,然而这种方式往往效率低下,甚至会导致刷题的质量很差。
学习概念是基础
不论是哪种算法或数据结构,它们都有一定的理论基础。例如,
哈希表
的原理是通过哈希函数将数据映射到一个位置上,这样查找效率会非常高。
而类似的还有
二叉树
、
二分查找
、
双指针
等重要概念。
在刷题之前,先花时间学习这些基础概念,可以让你在遇到相关题目时有更好的思路。
建议你通过书籍、课程或者文档去系统地学习这些概念。比如经典的《算法导论》、以及互联网平台上提供的免费教程都是不错的资源。打好基础,后续的题目就会变得相对容易理解。
看例题是提升理解的关键
在学习完理论知识后,接下来的步骤是通过一些例题来加深对概念的理解。
这一步不需要立刻开始自己写代码,而是先去分析和理解别人提供的解题思路。为什么要这样做呢?因为很多题目背后其实有非常固定的解法模式,而这些模式需要通过例题进行理解和巩固。
例如,我们学习了
双指针
这一技巧后,可以先通过阅读经典题目的解答,去理解双指针是如何在数组中被应用的。LeetCode 283“
移动零
”和 LeetCode 26“
删除有序数组中的重复项
”就是非常典型的双指针题目。
在你掌握了这些题目的解法后,再去尝试类似的题目,如 LeetCode 80“
删除有序数组中的重复项II
”,这样就可以巩固你的知识。
真正的练习才能扎实掌握
当你通过理论和例题建立了初步的理解后,最后的步骤才是自己动手去写代码进行练习。在这一阶段,最重要的是多写、多思考。不要只是简单地抄答案,而是每次在遇到问题时,先自己分析思路,然后再去比对解法。
练习的关键在于反复琢磨那些经典的题目和解法,因为这些题目背后的思路往往可以举一反三。
同时,建议你针对每一道题目都尽可能尝试多种解法,比如暴力解法、优化解法等。这样不仅能够加深对题目的理解,还能提升你在面试中的应对能力。
三、以经典题目为例的刷题实战
为了让刷题过程更具实践性,我们可以以具体的经典题目为例,进行详细分析。
LeetCode 283“移动零”
这是一道典型的双指针问题。题目要求我们将数组中的所有0移动到数组末尾,并且保持非零元素的相对顺序。使用双指针法可以高效地解决这道题目,一个指针用于遍历数组,另一个指针用于记录非零元素的位置。
def moveZeroes(nums):
j = 0 # j用于记录非零元素的位置
for i in range(len(nums)):
if nums[i] != 0:
nums[i], nums[j] = nums[j], nums[i]
j += 1
通过这道题目,可以理解双指针的核心思想,即通过两个指针来减少无用的遍历和交换操作。在理解了这道题的解法之后,我们可以进一步挑战类似的题目,如 LeetCode 485“
最大连续 1 的个数
”、LeetCode 26“
删除有序数组中的重复项
”,这些题目都可以通过双指针技巧来解决。