type
status
date
slug
summary
tags
category
icon
password
1. 二数之和2. 字母异位词分组3. 最长连续序列5. 盛最多水的容器6.三数之和 解法:双向双指针7. 接雨水8. 无重复字符的最长子串9. 找到字符串中所有字母异位词解法一:定窗口模版题10. 和为 K 的子数组解法:前缀和+哈希表模版题11. 滑动窗口最大值解法:单调队列模版题12. 最小覆盖子串解法:《长度最小+滑动窗口》系列模版题13. 最大子数组和解法:前缀和系列题动态规划解法14.合并区间解法:合并数组左右值的模版题15. 轮转数组解法:想到轮转就会想到:三次反转!16. 除自身以外数组的乘积解法:前缀积 + 后缀积系列题缺失的第一个正数矩阵置零链表相交链表解法:双链表相交最简方法反转链表解法:重要的反转链表模版,是其他题目的基础回文链表《翻转链表》模版+《寻找链表的中间节点》模版合并链表两数相加删除链表的倒数第 N 个结点解法:技巧题:双指针固定长度两两交换链表中的节点K 个一组翻转链表解法:反转链表的加强模版排序链表LRU缓存二叉树翻转二叉树二叉树的直径对称二叉树对于递归的总结二叉树的层序遍历解法:《层序遍历》模版题!死背!将有序数组转换为二叉搜索树解法:模板:构造二叉搜索树验证二叉搜索树解法:前序遍历和中序遍历的学习二叉搜索树中第 K 小的元素从前序与中序遍历序列构造二叉树解法:数组copy递归构造图论岛屿数量解法:图的DFS215. 数组中的第K个最大元素解法
1. 二数之和
- 使用HashMap:
- HashMap查询key的时候只需要进行计算hash值并查询,其时间复杂度是O(1)
2. 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
示例 2:
示例 3:
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
str.toCharArray()
方法,转为char数组- char数组转为字符串,用new String创建,不用toString()
map.getOrDefault()
方法,没有这个key就创建一个默认键值对
Arrays.sort()
3. 最长连续序列
给定一个未排序的整数数组
nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为
O(n)
的算法解决此问题。示例 1:
示例 2:
提示:
0 <= nums.length <= 105
109 <= nums[i] <= 109
- 要求O(n),所以这题缩减时间复杂度的操作:
- 构建HashSet去重,同时contains的复杂度O(1)
- 使用
if(!set.contains(num - 1))
来跳过筛选
5. 盛最多水的容器
给定一个长度为
n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。找出其中的两条线,使得它们与
x
轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。
说明:你不能倾斜容器。
6.三数之和
给你一个整数数组
nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。注意:答案中不可以包含重复的三元组。
解法:双向双指针
双向双指针的思路其实比较简洁:
- 数组排序
- 包含最大的数
m
和最小的数n
,设要求的数是num
- m+n > num; m - 1
- m+n < num; n + 1
- 如果要求不包含相同的数,记得跳过相同的数
7. 接雨水
给定
n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。示例 1:

示例 2:
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
- 使用双指针,也可以直接构建三个数组(前缀和后缀数组)
- 主要思路就是前缀和后缀和思路,将每一个数字对应的值看成一个左右两边两个板构建的桶
- 前缀就是前缀最大,代表左边板,同理,后缀代表右边板
- 按照较小的一边进行取值

8. 无重复字符的最长子串
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。示例 1:
示例 2:
示例 3:
提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
学习之处:
- 可以使用128位的数组进行对英文字母、数字、符号和空格(题目条件)的存储,比较方便编写代码。
- 当然如果题目规定只有小写字母,也可以使用26位数组
- 双指针法,同时注意:
- 先进行循环的判断,再更新结果值
9. 找到字符串中所有字母异位词
解法一:定窗口模版题
模版内容在注释中写明,具体流程如下:
- 设置两个大小相同的窗口,分别设为
a,b
,先对要进行检测的窗口b
进行初始化赋值
- 设置双指针left和right
- 开始循环
- 末端进入窗口a
- 判断窗口a长度是否等于窗口b,如果没有则
continue
- 开始更新答案
- 前端出窗口a
10. 和为 K 的子数组
给你一个整数数组
nums
和一个整数 k
,请你统计并返回 该数组中和为 k
的子数组的个数 。子数组是数组中元素的连续非空序列。
示例 1:
示例 2:
提示:
1 <= nums.length <= 2 * 104
1000 <= nums[i] <= 1000
107 <= k <= 107
解法:前缀和+哈希表模版题
- 前缀和列表
- 列表一定是单调的
- 假设
i+1
到j
之和为num
- 前缀和列表中
i
和j
前缀和列表两项之差也为num
- 哈希表
- 用于存储前缀和列表中数字出现的次数
- 这样就不需要分别进行遍历前缀和列表来进行求值
步骤:
- 生成前缀和列表
- 注意需要加上第0项,也就是前缀和为0的一项
- 前缀和列表每一项对应参数
int[] nums
的每一项的前缀和
- 声明hash表
- 键是前缀和列表的值,也就是
int[] nums
的每一项的前缀和; - 值是此前缀和出现的次数
- 对前缀和列表进行循环中,对每一个
preSum
,查询preSum-k
的值是否在hash表中 - 如果存在,则代表前缀和列表中存在两项
i
和j
,满足preSums[j]-preSums[i] = k
,如果不存在着返回0 - 将其添加在答案中
- 更新hash表中此前缀和
preSum
存在次数
11. 滑动窗口最大值
给你一个整数数组
nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。
示例 1:
示例 2:
提示:
1 <= nums.length <= 105
104 <= nums[i] <= 104
1 <= k <= nums.length
解法:单调队列模版题
遇到类似这种,需要根据单调队列的类型题,按照以下模版进行:
- 首先设置单调队列,初始化
- 入队列
- 判断新来的值是否大于队列末尾,如果是,则删除末尾
- 此步骤循环进行,知道末尾大于新值
- 添加新值进队列末尾
- 出队列
- 记得判断队列长度
- 记录答案
- 队首的值是最大值(或者最小值)
编程学习:
- 直接使用
- 将为空和不为空的情况一次性讨论了
- 注意是使用数组的下标作为队列中的参数
12. 最小覆盖子串
给你一个字符串
s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。
- 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
示例 2:
示例 3:
提示:
m == s.length
n == t.length
1 <= m, n <= 105
s
和t
由英文字母组成
解法:《长度最小+滑动窗口》系列模版题
- 当看到是
长度最小
,且可以涉及到滑动窗口
的时候,就要想到这个模版
- 设置
结果变量
,本例中就是ansLeft
和ansRight
- 比如在
最小的值
这种题目,就是设置一个比较大的值
- 进行一个计数数组的创建(有些题目不用)
- 在Java中,虽然也可以使用
HashMap
,但还是使用数组作为计数器更为快捷方便
- 设置
left
和right
,进行right
的for循环,在此循环中: - 进行
right
(右端点)对于结果的扩充,本例就是将右端点加入计数器 - 循环条件判断,判断条件和题目
最小的***
有关 - 在循环中更新left,同时也更新结果
- 对
结果变量
进行判断,如果没有变化,则证明是没有对结果进行更改,一般就是输出空值。
注意点:
- 出现满足条件的值之后,应该是while更新left值,使得left值尽量靠近右值
- 使用
for(int i = 'A'; i <= 'Z'; i++)
这样的条件来进行范围的缩小
- 记得关注
s.substring
的用法 - 此函数只有左右参数,左开右闭
13. 最大子数组和
给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组
是数组中的一个连续部分。
示例 1:
示例 2:
示例 3:
提示:
1 <= nums.length <= 105
104 <= nums[i] <= 104
解法:前缀和系列题
这里只我自己的解法,稍微有点复杂,后面贴上灵神的简便解法
说一说重点思路:
- 初始化一个前缀和数组,不必多说
- 当问题转化为求前缀和中
当前项和前项的最小之差
的时候 - 维护一个当前
最小值变量
- 每次循环使用当前值减
最小值变量
- 如果当前值比
最小值变量
小,那就更新最小值变量
灵神的简便做法:不需要进行前缀和的初始化
动态规划解法
在求数组子序列的时候最方便
14.合并区间
以数组
intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。示例 1:
示例 2:
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
解法:合并数组左右值的模版题
在所有进行数组的左右端合并的题目中都可以进行这个算法,算法步骤:
- 首先对数组按照左端点进行排序
- 目的是为了方便进行循环比较,只需循环判断后续左端点是否小于前序右端点
- 在java中进行排序是可以定义其排序方式,在第二个参数中使用lambda表达式表示即可
- 进行循环判断:
- 若当前ans数组是空,直接添加进ans列表
- 判断当前遍历的数组
item
的右端点是否小于等于ans中的最末尾的值的左端点 - 如果小于等于,则修改其ans值
- 如果是大于,则证明不可能有相交结果,把这个新纳入ans列表中
java语言的学习:
Arrays.sort(intervals, (p , q) -> p[0] - q[0]);
ans.get(m-1)[1] = Math.max(i[1],ans.get(m-1)[1]);
get值可以直接get数组引用,这样可以直接对其进行修改
15. 轮转数组
给定一个整数数组
nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。示例 1:
示例 2:
提示:
1 <= nums.length <= 105
231 <= nums[i] <= 231 - 1
0 <= k <= 105
解法:想到轮转就会想到:三次反转!
只要使用三次反转,就会像是轮转一样
- 小技巧:异或三连击作为交换两项值
- 在字符串中和在数组中都可以使用其方法
16. 除自身以外数组的乘积
给你一个整数数组
nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。题目数据 保证 数组
nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。请 不要使用除法,且在
O(n)
时间复杂度内完成此题。示例 1:
示例 2:
解法:前缀积 + 后缀积系列题
这种题目首先会考虑到前后缀积,这里给一个定义:
前缀积数组和后缀积数组:
- 定义
pre
[
i
]
表示从nums
[0]
到nums
[
i
−1]
的乘积。
- 定义
suf
[
i
]
表示从nums
[
i
+1]
到nums
[
n
−1]
的乘积。
注意:
- 这里和之前求前缀+哈希表的那题不一样,前缀和数组并没有多出一项
- 这是因为在当时的情况下需要求
pre[i] - pre[j]
这样的值,是因为在当时需要进行减法运算。 - pre[0] = 0 这一项的多出和链表中哨兵节点的原理是一样的,方便通过这个
基准点
进行减法 - 而前缀积的这种情况不需要进行类似减法的操作,所以说不需要多增添一项操作。
- 计算前缀积:
- 计算后缀积
缺失的第一个正数
给你一个未排序的整数数组
nums
,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为
O(n)
, 并且只使用常数级别额外空间的解决方案。示例 1:
示例 2:
示例 3:
提示:
1 <= nums.length <= 105
231 <= nums[i] <= 231 - 1
矩阵置零
提示:
m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
231 <= matrix[i][j] <= 231 - 1
链表
相交链表
给你两个单链表的头节点
headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。图示两个链表在节点
c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:

示例 2:

示例 3:

提示:
listA
中节点数目为m
listB
中节点数目为n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
- 如果
listA
和listB
没有交点,intersectVal
为0
- 如果
listA
和listB
有交点,intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度
O(m + n)
、仅用 O(1)
内存的解决方案?解法:双链表相交最简方法
两条路径进行相交,只需要让他们都进行向前遍历
- 如果为空,那就换为另一条链表,只要相交就代表他们遇到同一节点,或者是同一个空节点
- 同一节点代表存在值
- 空节点代表不存在相交节点
反转链表
解法:重要的反转链表模版,是其他题目的基础
很简单的题目,这里不做题目描述了
解法需要注意:
- pre最后是反转后的头节点,而cur最后为空
回文链表
给定一个head节点,判断一个链表是否为左右对称的表
示例 1:

示例 2:

《翻转链表》模版+《寻找链表的中间节点》模版
首先需要记住两个模版
- 一个是反转链表,上一题已经说过
- 一个是求链表的中间节点,是一个很有用的模版,利用的是快慢指针
- 快指针走两步,慢指针走一步
- 快指针后一项为空时,慢指针是
单数链表
的中间值 - 快指针为空时,慢指针是
双数链表
的第二个中间值

所以这题可以直接利用两个模版的叠加,将中间节点以后的链表直接反转,拆分为两个链表
然后看这两个链表中每个值是否一致
合并链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:

示例 2:
示例 3:
提示:
- 两个链表的节点数目范围是
[0, 50]
100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
解法没有什么模版,但有很多值得注意的编程技巧
编程技巧总结:
- 当发现最开始的节点需要单独讨论的时候,考虑使用
哨兵节点
,可以减少头节点的单独讨论
两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:

示例 2:
示例 3:
提示:
- 每个链表中的节点数在范围
[1, 100]
内
0 <= Node.val <= 9
- 题目数据保证列表表示的数字不含前导零
删除链表的倒数第 N 个结点
如题,就是删除链表的倒数第N个节点:
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
解法:技巧题:双指针固定长度
利用两个指针同时走的技巧:
- right指针先走
n
步
- left指针和right指针再同时进行移动
- 当right指针到达最后一个时,left指针刚刚到达所指节点的上一个节点
- 也就是倒数第n个节点的前一个节点
两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:

示例 2:
示例 3:
提示:
- 链表中节点的数目在范围
[0, 100]
内
0 <= Node.val <= 100
K 个一组翻转链表
给你链表的头节点
head
,每 k
个节点一组进行翻转,请你返回修改后的链表。k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
解法:反转链表的加强模版
这是之前做过的反转链表的加强版,
- 除了包含反转链表的过程之外,还添加了p0节点和哨兵节点
- p0节点,用于进行反转链表后,回归链表的处理
- 哨兵节点:由于处理头节点的时候,p0应该是在进行反转链表的前一个节点,如果不用哨兵节点的话,需要单独做一次处理,很不方便,所以使用哨兵节点进行处理。

注意这个图,这个图的下面部分就是p0节点负责的操作:
排序链表
给你链表的头结点
head
,请将其按 升序 排列并返回 排序后的链表 。示例 1:

示例 2:

示例 3:
提示:
- 链表中节点的数目在范围
[0, 5 * 104]
内
105 <= Node.val <= 105
LRU缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现
LRUCache
类:LRUCache(int capacity)
以 正整数 作为容量capacity
初始化 LRU 缓存
int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回1
。
void put(int key, int value)
如果关键字key
已经存在,则变更其数据值value
;如果不存在,则向缓存中插入该组key-value
。如果插入操作导致关键字数量超过capacity
,则应该 逐出 最久未使用的关键字。
函数
get
和 put
必须以 O(1)
的平均时间复杂度运行。示例:
提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 105
- 最多调用
2 * 105
次get
和put
二叉树
翻转二叉树
给你一棵二叉树的根节点
root
,翻转这棵二叉树,并返回其根节点。示例 1:

提示:
- 树中节点数目范围在
[0, 100]
内
100 <= Node.val <= 100
对二叉树自身的操作,用
辅助函数+前序遍历
最为稳妥二叉树的直径
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点
root
。两节点之间路径的 长度 由它们之间边数表示。
示例 1:

示例 2:
提示:
- 树中节点数目在范围
[1, 104]
内
100 <= Node.val <= 100
对称二叉树
对于递归的总结
- 首先需要根据
递归三要素
判断这个递归样式的大概样子,三要素分别是: - 递归函数的返回值类型和输入参数
- 确定终止条件,也就是递归函数什么时候可以返回
- 确定单层递归的逻辑
if(left == null || right == null) return left == right;
- 这个边界点的判断很厉害,用简洁的代码实现了空值时候的判断操作
灵神的代码更是简洁
二叉树的层序遍历
给你二叉树的根节点
root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。示例 1:

解法:《层序遍历》模版题!死背!
将有序数组转换为二叉搜索树
给你一个整数数组
nums
,其中元素已经按 升序 排列,请你将其转换为一棵 平衡 二叉搜索树。示例 1:


示例 2:

解法:模板:构造二叉搜索树
其实在这种构造搜索树的题目中,运用前序遍历最为方便
大致方法背下来就好
- 首先注意在前序遍历的时候
重点关注
- 前序遍历中
参数的类型一般会多带几个约束
: - 本题的约束是:左右节点范围,在进入左右子树的时候会附带一个左右边界值,用这个值进行判断
验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:

示例 2:

提示:
- 树中节点数目范围在
[1, 104]
内
231 <= Node.val <= 231 - 1
解法:前序遍历和中序遍历的学习
这个题居然可以被看做是用来理解前序和中序遍历的题目。(后序遍历也可以,只是逻辑有点难懂,我觉得懂前两种也够用了)
- 首先来进行前序遍历
- 正如之前说的,对于前序遍历来说,递归参数一般都需要带点除了node节点之外的值,本题也是其中之一
- 将左右边界加入传递参数中
- 再来进行中序遍历
- 中序遍历灵神给了一个比较新的代码模板,具体是这样:
- 为什么说它比较新?因为它展示了一个中序遍历的思路:
- 左子树递归,判断返回值,直接返回
- 中间进行当前递归的逻辑
- 直接返回右子树递归值
- 这样的思路,当进行需要使用到左右子树返回值的递归的时候就比较有用
- 因为可以左右子树分别进行返回
- 如果左子树一开始就判断有问题,直接不进行递归了,直接返回
- 右子树同理
- 这样的中序遍历比较容易记住
- 当然,这个中序遍历还有一个在二叉搜索树中很重要的技巧:
- 使用
pre
结点!此节点放在全局中,当进行二叉搜索树的遍历的时候,非常方便!!
二叉搜索树中第 K 小的元素
给定一个二叉搜索树的根节点
root
,和一个整数 k
,请你设计一个算法查找其中第 k
小的元素(从 1 开始计数)。示例 1:

示例 2:

提示:
- 树中的节点数为
n
。
1 <= k <= n <= 104
0 <= Node.val <= 104
解法:全局变量+void直接遍历 真的方便
从前序与中序遍历序列构造二叉树
给定两个整数数组
preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1:

解法:数组copy递归构造
来自灵神的解法,使我受益匪浅
- 首先从前序数组中,第一个数就是目前树的root节点。
- 根据这个root节点在中序遍历的位置,可以将中序和前序数组拆分为两个数组
- 两个数组代表左子树和右子树
- 通过递归,再返回左右子树的方式,构造二叉树
- 学习之处:
Arrays.copyOfRange()
操作,可以复制数组为新数组,是一个很好的工具类for(int i = 0 ; ; i++){
- 这样设计的时候,编译器会认为这是个无限循环,并且默认
循环中的if条件一定能达成
,所以不会强制要求循环外面需要有个return值
图论
岛屿数量
解法:图的DFS
这里用一个例题来说明图中的DFS遍历是什么样子
给你一个由
'1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
示例 2:
解法如下:
215. 数组中的第K个最大元素
给定整数数组
nums
和整数 k
,请返回数组中第 k
个最大的元素。请注意,你需要找的是数组排序后的第
k
个最大的元素,而不是第 k
个不同的元素。你必须设计并实现时间复杂度为
O(n)
的算法解决此问题。示例 1:
示例 2:
提示:
1 <= k <= nums.length <= 105
104 <= nums[i] <= 104