本文对应该如何刷 LeetCode
的姿势进行不完全总结 1’ 2。
更新:2023 / 2 / 4
总览
LeetCode
3’ 4 题目很多,因此不必全部刷完,最佳策略是基于知识点分类后刷经典题,举一反三。
常见的类型如下:
滑动窗口
Sliding Window
,滑动窗口
,经常是用来执行数组或是链表上某个区间(窗口)上的操作,比如找最长的全为 1
的子数组长度,如下图所示:
判断一个问题是否属于 滑动窗口
问题的诀窍:
滑动窗口
问题的输入是一些线性结构,比如链表、数组、字符串等,目的是求最长/最短字符串或是某些特定的长度要求的元素的字符串等。滑动窗口
一般从第一个元素开始,一直往右边一个一个元素挪动。
窗口大小则根据题目要求,可能为固定大小,也可能存在变化。
双指针
Two Pointer
,双指针
,分为同向双指针和异向双指针,通常用在排好序的数组或是链表中朝着左右方向移动寻找对子,直到它们有一个或者两个都满足某种条件。
比如,你需要去比较熟组中每个元素和其他元素的关系时就需要用到双指针,如下图所示:
我们需要双指针的原因是因为如果你只用一个指针的话,得来回跑才能在数组中找到你需要的答案,而这一个指针来来回回的过程就很耗时和浪费空间了 —— 这是考虑算法的复杂度分析的时候的重要概念。虽然 brute force
一个指针的解法可能会奏效,但时间复杂度一般会是 O(n^2)
。在很多情况下,双指针能帮助我们找到空间或是时间复杂度更低的解。
判断一个问题是否属于 双指针
问题的诀窍:
- 一般来说,数组或是链表是排好序的,你得在里头找一些组合满足某种限制条件。
这种组合可能是一对数,三个数,或是一个子数组。
快慢指针
Fast & Slow pointers
,快慢指针
,这种算法的两个指针在数组上(或是链表上、序列上)的移动速度不一样,这种方法在解决有环的链表和数组时特别有用,如下所示:
通过控制指针不同的移动速度(比如在环形链表上),这种算法证明了它们一定会相遇。快的指针肯定会追上慢的一个(可以想象成跑道上面跑得快的人套圈跑得慢的人)。
判断一个问题是属于 快慢指针
问题的诀窍:
- 需要处理环上的问题,比如环形列表和环形数组。
- 需要知道链表的长度或是某个特别位置的信息时。
双指针 V.S 快慢指针
有些情形下,不应该用双指针。比如,我们在单链表上不能往回移动时。
一个典型的需要用到快慢指针的模式是当你需要去判断一个链表是否是回文的时。
区间合并
Merge Intervals
,区间合并
,是一个用来处理有区间重叠的很高效的技术。
在涉及到区间的很多问题中,通常咱们需要要么判断是否有重叠、要么合并区间。
以2个区间 a
和 b
为例,他们之间的关系能跑出 6
种情况,如下所示:
判断一个问题是属于 区间合并
问题的诀窍:
- 需要产生一堆相互之间没有交集的区间时。
- 需要重叠区间时
循环排序
Cyclic Sort
,循环排序
,可以用来将数组中的数值限定在一定区间。
如果通过遍历数组中的元素,发现当前这个数它不在其应该在的位置然后把它同它应该在的那个位置上的数交换一下,复杂度就会是 O(n^2)
,这时循环排序的优势就体现出来了,如下图所示:
判断一个问题是属于 循环排序
问题的诀窍:
- 一般涉及到排序好的数组,而且数值一般满足于一定的区间;
- 可能需要在排好序 / 翻转过的数组中寻找丢失的 / 重复的 / 最小的元素
链表翻转
In-place Reversal of a LinkedList
,链表翻转
,用于在某些情况下翻转列表中的某一段的节点。通常是要求原地反转,即重复使用这些已建好的节点而不使用额外的空间。
这种模式每次就翻转一个节点。一般需要用到多个变量,一个变量指向头节点(下图中的 current
),另外一个( previous
)则指向咱们刚刚处理完的那个节点。
在这种特定步长的方式下,你需要先将当前节点( current
)指向前一个节点( previous
),再移动到下一个。同时,你需要将上一个( previous
)总是更新到你刚刚新鲜处理完的节点,以保证正确性。如下所示:
判断一个问题是属于 链表翻转
问题的诀窍:
- 当需要翻转链表且不能使用额外空间时。
参考链接
#todo