本文主要记录了博主的 Leetcode 算法刷题记录,方便以后查询复习。
哈希表
两数之和
这道题首先考虑的是通过两层for
循环遍历查询,但这样有很多无意义的查找。因此我们可以采用哈希表来减少查找的时间复杂度,此时查找顺序是往后而不是往前。注意使用Map
对象来减少查询之前数据的时间复杂度,这里不能使用WeakMap
,因为WeakMap
只能使用对象作为键。
字母异位词分组
建立一个判断某一字符串是否是同一组的函数,再遍历数组,填充键-数组的Map
对象,然后利用Map
对象的相关方法返回实例。关键在于如何判断是同一种类型的字符串,一种是将字符串转为数组后排序,然后生成唯一的key
,排序算法时间复杂度 $O(nlogn)$ 另一种方法是使用一个26
位的数组来构造一个字符串的唯一键,来实现匹配的目的,注意使用的是charCodeAt
方法,此种方法的时间复杂度为 $O(n)$,$n$ 为字符串的长度。
最长连续序列
首先采用以数组为参数构建set
,在建立每个元素哈希的同时去重,后续遍历在set
集合中展开,然后找到每段区间的起始位置并求其最大值,即存在比起小的数字即set.has(num - 1)
这种情况需要跳过,因为必然会从比num - 1
开始遍历的区间的长度要小。
总结
在 JS 中,哈希表主要采用Map
和Set
这两种内置的数据结构。合理使用这两种数据结构可以极大地方便查找元素,从而降低算法的时间复杂度。对于Map
对象来说更是可以把键与值关联起来存储,方便算法的改进,还可以用于实际需求,如分组的运用。
双指针
移动零
代码中left
指针指向已处理好的数组的末尾后一个元素,即指向现数组的第一个0
,当right
指针指向非零数字的时候就将非零数字提到前面然后进行交换。核心思路为left
指针始终指向第一个0
元素,当right
指针指向遇到非零元素时left
指针才会变化。
盛水最多的容器
双指针向内遍历,由于横向长度的变小,想要大于之前的水量,只能遇到更高的容器壁,所以小的容器壁一边需要往内收缩,不断更新最大值,最后得到相应的结果。感觉核心的思想还是贪心。🤔
三数之和
这道题目的难点在于去重。将数组进行排序是一个很巧妙的做法,一方面可以便于第一层i
使用一次后去重,第二个是便于l
和r
双指针向内遍历去重,同时在nums[i] > 0
的时候可以退出循环,避免无用的计算。做这道题重点在于两次去重的过程,值得仔细理解。
接雨水
接雨水似乎是写烂了的动态规划?🤔 下面也列出了三种方法,分别是:
- 动态规划双向遍历得到左右数组,然后逐个元素求解。
- 单调栈求解。注意栈中存储的是数组的索引。
- 动态规划 + 双指针,两边取最小求解,
leftMax
和rightMax
那个最小就确定了那个最小的值。
总结
双指针向内收缩对于特殊情况的元素遍历有很大的帮助,对于两个或两个以上元素的遍历,都可以考虑双指针的做法。
滑动窗口
无重复字符的最长字串
利用set
集合与滑动窗口解决,右扩张,左收缩,遍历数组获得最长长度。有点像滑动窗口 + 哈希表 + 双指针的结合。🤩
找到字符串中所有字母的异位词
主要采取长度为26
位的数组生成唯一的key
值,用来滑动窗口进行匹配。注意对一个元素进行处理或者对最后增减做一个条件判断。
总结
滑动窗口分为静态大小的窗口和动态大小的窗口,前者每次都需要变化,大小固定;后者一般为右扩张左收缩,大小不固定。
子串
和为 k 的子数组
注意是连续的子数组,可以采用两层for
循环,内层for
循环往回遍历。或者使用哈希表,注意哈希表的思路比较巧妙,遍历到当前元素的时候,采取看之前的Map
对象中是否存在当前和减去目标值,即map.has(pre - k)
,存在即计数增加,注意需要设置0
的初始值const map = new Map([[0, 1]])
。暴力做法可帮助理解第二种做法 🦀。
滑动窗口最大值
有点类似单调栈?🤔 采用单调队列求解,但需要注意无论是队列还是栈,数组中存放的都是下标,推入的元素需要根据题目条件来做处理。
最小覆盖字串
采用哈希表计数,然后采用不定长度滑动窗口求解,右扩张,左收缩,哈希表计数是关键中的关键。
总结
子串很多都与哈希表相关,注意总结题目经验求解,很多情况下可能用到滑动窗口,单调栈与单调队列的思想也很重要。
普通数组
最大子数组和
比较简单的动态规划题目,若前面的pre
小于 0 则直接舍去,防止拖累变小,感觉也有点贪心的思想在里面。🤔
合并区间
简单的模拟策略但需要注意排序的时候,在前面的右侧值不一定最大,所以需要取二者的最大值。
轮转数组
可采用额外数组存储,或者数组反转进行解决。第一种方法比较常规,第二种方法技巧性强但便于记忆与理解。😊
操作 |
结果 |
原始数组 |
1 2 3 4 5 6 7 |
翻转所有元素 |
7 6 5 4 3 2 1 |
翻转 $ [0, k\bmod n - 1] $ 区间的元素 |
5 6 7 4 3 2 1 |
翻转 $ [k\bmod n, n - 1] $ 区间的元素 |
5 6 7 1 2 3 4 |
除自身以外数组的乘积
类似接雨水双向数组,可采取遍历的方式进行反向计算。可以最大程度上节省空间。
缺失的第一个正数
原地哈希,将数组的值i
与下标i - 1
相对应,没有对应的元素证明是第一个缺失的正整数。注意第三个判断条件nums[i] !== nums[nums[i] - 1]
添加一个nums
判断可以应对数组中重复值的情况。
总结
数组的技巧性比较强,一般会与模拟,动态规划,哈希表结合,需要灵活使用。
矩阵
矩阵置零
常规的遍历标记然后取值,可以优化一下空间复杂度
螺旋矩阵
核心思路为模拟,设置上下左右 4 个变量便于理解,for
循环的时候采用变量命名的方式提醒自己是行还是列,同时注意left <= right && top <= bottom
与left < right && top < bottom
这两个判断条件的使用。
旋转图像
可以采用额外空间来一一对应,或者采用特殊技巧,上下反转后再对角线反转。
搜索二维矩阵
定位到右上角,利用矩阵递增的性质,实现 Z 字形查找
总结
矩阵的题目比较考验技巧与空间思维,需要严格分清行坐标与列坐标,写的时候需要思路清晰,一步一步来。