单调栈
单调栈主要解决以下几个问题:
- 比当前元素更大的下一个元素
- 比当前元素更大的前一个元素
- 比当前元素更小的下一个元素
- 比当前元素更小的前一个元素
寻找比当前元素更大的下一个元素或者下一个元素,那么应该使用栈底到栈顶由大到小的单调栈。当某个元素大于栈顶元素,将栈顶元素弹出,而该元素就是栈顶元素的最右侧的第一个大于它的元素。最左侧的最大元素,那么,某个元素在栈中的下一个元素就是大于它的第一个元素。
寻找比当前元素更小的下一个元素或者前一个元素,则使用栈底到栈顶由小到大的单调栈。
496.下一个更大元素
给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。
1 | /** |
503.下一个更大元素2
1 | /**给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素), |
556.下一个更大元素
1 | /** |
739.每日温度
下一个更大元素问题,单调递增栈
1 | /** |
901.股票价格跨度
1 | /** |
84. 柱状图中最大的矩形
1 | //本质就是找到某个元素左侧第一个小于它的元素,以及右侧小于它的第一个元素 |
85. 最大矩形
本题与上一题类似
1 | /** |
42. 接雨水
1 | //使用单调递减栈,假如某个元素大于栈顶元素,因为元素栈底到栈顶是由大到小的,所以必定可以形成雨水区域 |
单调队列
滑动窗口最大值
1 | /** |
滑动窗口变形

1 | /** |
滑动窗口处理子串的问题
关于处理最小字串,最长字串等问题,可以使用滑动窗口来解决。
1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
2、我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。
这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。
1 | string s, t; |
76. 最小覆盖子串
1 | /** |
3. 无重复字符的最长子串?
1 | public int lengthOfLongestSubstring(String s) { |
跳表
多级索引+链表。空间复杂度O(n),查找以及插入时间复杂度O(logn)。
对于链表的查找,常见的形式是O(n)的复杂度,需要遍历链表。

因此,可以抽取索引来解决。这样的话,时间复杂度就可以缩短一半。
skiplist正是受这种多层链表的想法的启发而设计出来的。
他会为每一个节点随机生成一个层数。

- 跳表实际上是通过多级索引来达到一个链表的二分查找的目的。
- 每个元素level是随机生成的。
- 最底层包含所有元素。
- 跳表查询、插入、删除的时间复杂度为O(log n)
- 每个元素内部是一个list,例如,7就是一个由4个元素组成的list。
查找数据
查找时间复杂度是O(logn)。
从最高层开始,一层层遍历直到原始链表。

最高层索引:从head开始,head下一个元素未7,所以后移。7下一个未null,所以位于7,null之间,下沉。
二层索引:从7开始,7下一个元素是37,23位于7,37之间。下沉。
一级索引:从7开始,7下一个元素19,23不位于,向右移动,19下一个元素37,23位于。下沉。
原始链表:从19开始,19下一个元素是22,23不位于,向右移动,22下一个元素26,找到23位置。
插入数据
当插入数据的时候,根据该点生成的层数level,从level层开始,逐层开始插入。跟查找整个过程相似,只不过添加了一个插入元素的过程。
插入数据6,level=3。
三级索引,1<6<13,所以往下移动。
二级索引,6位于1,7之间,所以,直接在1,7之间插入7。
一级索引,6不在1,4之间,所以向后移动,6位于4,7之间,所以插入。
原始链表,6不位于4,5之间,向后移动,6位于5,7之间,直接插入。
删除数据
与查找,插入数据类似。时间复杂度也是O(logn)。
LRU和LFU
LRU:当容量满的时候,删除最久未使用的元素。
LFU:当容量满的时候,删除使用次数最少的,假如使用次数最少有多个元素,那么删除最久未使用的元素。
LRU
146.设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
1 | /** |
LFU
当容量满的时候,删除的是访问次数最少的,假如有多个访问次数最少的节点,删除最久未访问的。
多个桶组成,每个桶代表的是访问次数的数值,桶内存储的是相同访问次数的所有节点,采用头插法,所以尾节点是该桶内最久未访问的节点。
各个桶之间也是使用双向链表来进行存储。
因此,需要定义一个Node:key,value,times(访问次数),pre,next.
两个map,一个存储key与Node,一个存储Node属于哪一个桶。
插入数据:假如map中包含该元素,那么更新value,访问次数++,并将该元素移动到访问次数+1桶。假如没有该元素,直接将该元素插入到访问次数为1的桶。假如超过了size,那么删除第一个桶的尾节点。
get数据:访问次数++,移动到访问次数+1的桶。
并查集
检查两个元素是否属于一个集合。
合并两个元素各自所在的所有集合。
并查集需要一次性将所有的数据传给他.
1 | public class UnionFind<K> { |
前缀树
经常用于统计和排序大量的字符串(但不仅限于字符串)。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
前缀匹配
trie树前缀匹配常用于搜索提示。如当输入一个网址,可以自动搜索出可能的选择。当没有完全匹配的搜索结果,可以返回前缀最相似的可能
字符串检索
给定一组字符串,查找某个字符串是否出现过。
1 | public class Trie { |