前言:
最近从大厂出来看机会,大厂面试基本都考察算法,于是维护此文档,一是查缺补漏,确保整体热点算法题目的应知应会,与思路的灵活理解;二是分享出来给其他同学朋友做一个参考借鉴,共同学习成长
目前刷题方向侧重于各大厂热点题,以及力扣的Hot 100
此笔记持续更新。
文章目录
- 一、菜单
- 未分类
- 树
- 栈
- 回溯
- 二分
- 双指针
- 双指针
- DP
- 贪心
- 二、刷题记录
- 1. 两数相加——哈希——一刷
- 2. 两数相加——链表——二刷
- 3. 无重复字符的最长子串——滑动窗口——三刷
- 5.最长回文串——数组
- 11. 盛水最多的容器——双指针——二刷
- 15. 三数之和——数组——二刷
- 19. 删除链表的倒数第n个节点——链表
- 20. 有效的括号——栈——二刷
- 21. 合并两个有序链表——链表-二刷
- 22. 括号生成——DFS&回溯*
- 23. 合并k个升序链表——链表&归并——二刷
- 24. 两两交换链表中的节点——链表——二刷
- 25. k个一组翻转链表——链表*——一刷
- 27. 移除元素——双指针——一刷
- 33. 搜索旋转排序数组——二分*
- 34. 在排序数组中查找元素的第一个和最后一个位置——二分——二刷
- 35. 搜索插入位置——二分——二刷
- 41. 缺失的第一个正数——数组——二刷
- 45. 跳跃游戏二——贪心*
- 46. 全排列——回溯&DFS——二刷
- 48. 旋转图像——矩阵——一刷
- 49. 字母异位词分组——哈希——二刷
- 53. 最大子数组和——贪心
- 54. 螺旋矩阵——矩阵——二刷
- 55. 跳跃游戏——贪心——二刷
- 56. 合并区间——数组*——二刷
- 70. 爬楼梯——DP——二刷
- 72. 编辑距离——二维DP——二刷
- 74. 搜索二维矩阵
- 75. 颜色分类——技巧*
- 78. 子集——DFS&回溯*
- 79. 单词搜索——DFS&回溯*
- 82. 删除链表中的重复元素2
- 83. 删除链表中的重复元素1
- 94. 二叉树的中序遍历——树——三刷
- 98. 验证二叉搜索树——树&递归——二刷
- 101. 对称二叉树——递归&树
- 102. 二叉树的层序遍历——树的广搜*
- 104. 二叉树的最大深度——广搜——三刷
- 105. 从前序与中序遍历序列构造二叉树——树&递归*
- 108. 将有序数组转换为二叉搜索树——树&递归&二分——三刷
- 114. 二叉树展开为链表——树&规律——二刷
- 118. 杨辉三角——DP——二刷
- 121. 买卖股票的最佳时机
- 124. 二叉树中的最大路径和——树&递归——一刷
- 128. 最长连续序列——技巧*
- 131. 分割回文串——回溯——二刷
- 136. 只出现一次的数字——技巧
- 139. 单词拆分——DP*
- 141. 环形链表——链表-二刷
- 146. LRU
- 153. 寻找旋转排序数组中的最小值——二分*
- 155. 最小栈——栈——二刷
- 160. 相交链表——链表
- 169. 多数元素——技巧*
- 189. 轮转数组——数组——二刷
- 198. 打家劫舍——DP——二刷
- 199. 二叉树的右视图
- 206. 反转链表——链表-三刷
- 215. 数组中第K个最大的元素——堆*
- 226. 翻转二叉树——树&递归——三刷
- 230. 二叉搜索树中第k小的元素
- 234. 回文链表——链表*——三刷
- 236. 二叉树的最近公共祖先——树&递归——二刷
- 238. 除自身以外数组的乘积——数组——二刷
- 240. 搜索二维矩阵二——矩阵&技巧——一刷
- 279. 完全平方数——DP*
- 283. 移动零——双指针
- 287. 寻找重复数——二分*-2
- 300. 最长递增子序列——DP*
- 322. 零钱兑换——DP*
- 347. 前k个高频元素
- 415. 字符串相加——大数——一刷
- 437. 路径总和三——树&DFS*
- 438. 找到字符串中所有字母异位词——滑动窗口&异位词——一刷
- 543. 二叉树的直径——递归——二刷
- 560. 和为k的子数组——前缀和*——二刷
- 718. 最长重复子串——DP——一刷
- 763. 划分字母区间——贪心*
- 1143. 最长公共子序列——二维DP*
- 三、大公司高频题
- 字节
- 四、高频题分类
- 链表高频题
- 树高频题
- 回溯高频题
- 二分高频题
- 栈高频题
一、菜单
未分类
序号 | 题目链接 | 题目类型 | 来源 | 备注 |
---|---|---|---|---|
1. 两数相加 | 哈希 | 力扣Hot 100 | 一刷20240906、二刷20241006 ; 水题 、一二刷直接秒 | |
49. 字母异位词分组 | 哈希&异位词 | 力扣Hot 100 | 一刷20241006 、二刷20241016; 水题 、一二刷直接秒 | |
128. 最长连续序列 | 哈希&技巧 | 力扣Hot 100 | 一刷20241006 、 二刷20241016; 思路很精彩,一刷完全看题解,二刷花了点时间,思考明白了时间复杂度为什么是On 。建议隔一个月三刷 | |
283. 移动零 | 双指针&技巧 | 力扣Hot 100 | 一刷20241006、二刷20241016; 做法多样,思路很精彩,尤其是二刷思路 ;建议隔十五天三刷 | |
11. 盛最多水的容器 | 贪心&双指针 | 力扣Hot 100 | 一刷20241006、二刷10141016; 一刷看了题解,二刷直接秒 | |
15. 三数之和 | 双指针 | 力扣Hot 100 | 一刷时间20241006、二刷时间20241015; 一刷看了题解,二刷花了点时间做出来了,建议隔一个月三刷 | |
3. 无重复字符的最长子串 | 滑动窗口 | 力扣Hot 100 | 一刷20240912、二刷20240917、三刷20241015 一刷题解,二刷花时间,三刷秒杀,建议隔一个月四刷 | |
438. 找到字符串中所有字母异位词 | 滑动窗口&异位词 | 力扣Hot 100 | 一刷20241016; 滑动窗口裸题,建议隔一个月二刷 | |
560. 和为k的子数组 | 前缀和&哈希 | 力扣Hot 100 | 一刷20241009、二刷20241017 经典前缀和,一刷二刷都卡了很久,建议隔十五天三刷 | |
53. 最大子数组和 | 贪心 | 力扣Hot 100 | 一刷20240928、二刷20241017 直接秒,解出不难,难得是面试时可能用例不可见,需要处理细节,可以三刷 | |
56. 合并区间 | 数组 | 力扣Hot 100 | 一刷20240929、二刷20241017 一二刷都看题解了,一是排序函数,二是一些细节处理,建议隔一个月三刷 | |
189. 轮转数组 | 数组&技巧 | 力扣Hot 100 | 一刷20240929、二刷20241017 一刷看题解,二刷直接秒 | |
238. 除自身以外数组的乘积 | 数组&技巧 | 力扣Hot 100 | 一刷20240929、二刷20241017 一刷看题解,二刷想了会秒杀,可以三刷 | |
41. 缺失的第一个正数 | 数组&技巧 | 力扣Hot 100 | 一刷20240929、二刷20241017 一刷看题解,二刷想了会秒杀,可以三刷 | |
54. 螺旋矩阵 | 矩阵 | 力扣Hot 100 | 一刷20241009、二刷20241017 一刷看题解,二刷有调试,整体秒掉了,可以三刷 | |
48. 旋转图像 | 矩阵&规律 | 力扣Hot 100 | 一刷20241017 一刷看了下题解,核心掌握矩阵对角、水平、垂直翻转,建议隔一个月三刷 | |
240. 搜索二维矩阵二 | 矩阵&规律 | 力扣Hot 100 | 一刷20241017 偏技巧题,建议隔一个月二刷 | |
160. 相交链表 | 链表 | 力扣Hot 100 | 一刷20240917、二刷20241011 一刷看题解,二刷直接秒 | |
206. 反转链表 | 链表 | 力扣Hot 100 | 一刷20240919、二刷20241011、三刷20241018 直接秒 | |
234. 回文链表 | 链表 | 力扣Hot 100 | 一刷20240919、二刷20241011、三刷20241017 一刷二刷都比较费劲,三刷比较轻松,这个题锻炼链表的边界感很好,建议隔三个月三刷 | |
141. 环形链表 | 链表&技巧 | 力扣Hot 100 | 一刷20240908、二刷20241011 懂快慢指针思路,直接秒 | |
21. 合并两个有序链表 | 链表 | 力扣Hot 100 | 一刷20240921、二刷20242012 裸题,直接秒 | |
2. 两数相加 | 链表 | 力扣Hot 100 | 一刷20240921、二刷20241017 有大数运算、链表合并的影子,直接秒 | |
19. 删除链表的倒数第n个节点 | 链表 | 力扣Hot 100 | 一刷20240921、二刷20241017 一刷看题解,二刷直接秒 | |
24. 两两交换链表中的节点 | 链表 | 力扣Hot 100 | 一刷20241005、二刷20241017 裸题,直接秒 | |
25. k个一组翻转链表 | 链表 | 力扣Hot 100 | 一刷20241018 hard,反转链表的变形题,细节更多,需进一步熟练,建议隔2天3刷 | |
23. 合并k个升序链表 | 链表&归并 | 力扣Hot 100 | 一刷20241018、二刷20241023 hard,但是归并+链表裸题,二刷直接秒了,建议隔一个月三刷 | |
146. LRU | 链表 | 力扣Hot 100 | 一刷20240915、二刷20240917、三刷20241018 三刷略微调试了一下过的,但是忘记put和get都需要moveToHead了,建议隔一个月四刷 |
树
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
94. 二叉树的中序遍历 | 树&递归 | 力扣Hot 100 | 一刷20240919、二刷202040921、三刷20241018 前中后序遍历无脑递归,直接秒,递归感不够的话只能多看多写 | |
104. 二叉树的最大深度 | 树&层序遍历 | 力扣Hot 100 | 一刷20240910、二刷20240921、三刷20240929、四刷20241018 前两次还有点磕绊,现在层序遍历直接秒 | |
226. 翻转二叉树 | 树&递归 | 力扣Hot 100 | 一刷20240929、二刷20241007、三刷20241018 对树,我一般是除了层序遍历都用递归,前两刷应该还看了题解,三刷直接秒,极致丝滑 | |
101. 对称二叉树 | 树&递归 | 力扣Hot 100 | 一刷20240929、二刷20241007、三刷20241018 一二刷应该看了题解,三刷直接秒,递归做多自然就推出来了,还是很丝滑 | |
543. 二叉树的直径 | 树&递归 | 力扣Hot 100 | 一刷20240930、二刷20241018 一刷看题解,二刷看了些提示,建议隔一个月三刷 | |
102. 二叉树的层序遍历 | 树&层序遍历 | 力扣Hot 100 | 一刷20240921 层序遍历裸题,直接秒 | |
108. 将有序数组转换为二叉搜索树 | 搜索树&递归&二分 | 力扣Hot 100 | 一刷20240930、二刷20241007、三刷20241018 二分+递归,三刷直接秒,比较丝滑 | |
98. 验证二叉搜索树 | 搜索树&递归 | 力扣Hot 100 | 一刷20240921、二刷20241019 二刷递归直接秒,很丝滑 | |
230. 二叉搜索树中第k小的元素 | 搜索树&递归 | 力扣Hot 100 | 一刷20240922 思路完全同上,直接秒 | |
199. 二叉树的右视图 | 树&层序遍历 | 力扣Hot 100 | 一刷20240930 层序遍历裸题,直接秒 | |
114. 二叉树展开为链表 | 树&模拟 | 力扣Hot 100 | 一刷202040930、二刷20241029 二刷看了思路,可以三刷 | |
236. 二叉树的最近公共祖先 | 树&递归 | 力扣Hot 100 | 一刷20241001、二刷20241019 二刷还是看了题解,对最近公共祖先的理解不到位,建议15天内三刷 | |
124. 二叉树中的最大路径和 | 树&递归 | 力扣Hot 100 | 一刷20241019 hard,类似二叉树直径,更复杂,建议15天内二刷 | |
栈
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
20. 有效的括号 | 栈 | 力扣Hot 100 | 一刷20240919、二刷20241022 一刷看了思路,二刷直接秒 | |
155. 最小栈 | 栈 | 力扣Hot 100 | 一刷20240919、二刷20241029 二刷秒 | |
回溯
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
46. 全排列 | 回溯 | 力扣Hot 100 | 一刷20241003、二刷20241029 二刷debug了,还是不太熟练 | |
131. 分割回文串 | 回溯 | 力扣Hot 100 | 一刷20241006、二刷20241029 二刷秒杀 | |
二分
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
35. 搜索插入位置 | 二分 | 力扣Hot 100 | 一刷20240917、二刷20241029 二分模板题,直接秒 | |
34. 在排序数组中查找元素的第一个和最后一个位置 | 二分 | 力扣Hot 100 | 一刷20240921、二刷20241029 二刷秒杀 | |
33. 搜索旋转排序数组 | 二分&技巧 | 力扣Hot 100 | 一刷20241003、二刷20241029 二刷秒杀 |
双指针
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
27. 移除元素 | 双指针 | 字节高频题 | 一刷20241022 一刷看了思路,建议隔一个月二刷 | |
双指针
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
215. 数组中第k个最大元素 | 堆 | Hot 100 | 一刷20240922、二刷20240928、三刷20241031 根堆裸题,三刷秒杀 | |
347. 前k个高频元素 | 堆 | Hot 100 | 一刷20240928 根堆裸题 | |
DP
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
70. 爬楼梯 | DP | 力扣Hot 100 | 一刷20241007、二刷20241109 DP裸题, 二刷秒 | |
72. 编辑距离 | 二维DP | 力扣Hot100&字节高频题 | 一刷20241008、二刷20241022 一二刷都看了思路,建议隔一个月三刷 | |
118. 杨辉三角 | DP | 力扣Hot 100 | 一刷20241007、二刷20241109 二刷秒,需注意边界处理 | |
198. 打家劫舍 | DP | 力扣Hot 100 | 一刷20241007、二刷20241109 DP裸题,二刷秒 | |
718. 最长重复子数组 | DP | 字节高频题 | 一刷20241023 一刷看了思路,建议隔一个月二刷 | |
贪心
序号 | 题目链接 | 标签 | 来源 | 备注 |
---|---|---|---|---|
121. 买卖股票的最佳时机 | 贪心 | 力扣Hot100 | 一刷20240928、二刷20241031 二刷秒 | |
55. 跳跃游戏 | 贪心 | 力扣Hot 100 | 一刷20240928、二刷20241031 二刷想了想思路,做掉的 |
二、刷题记录
1. 两数相加——哈希——一刷
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
利用Hash表来同时存储值和下标,值作为key,下标作为value,考虑到可能存在相同的数, 所以value是List类型,如下
但是这样遗漏了一个有效条件,也就是只能存在两数之和相加, 所以我们直接在第一遍循环里做判断, 可以节省一部分空间, 即:
class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map = new HashMap<>();int[] res = new int[2];for (int i = 0; i < nums.length; i++) {int temp = target - nums[i];if (map.containsKey(temp)) {res[0] = i;res[1] = map.get(temp);} else {map.put(nums[i], i);}}return res;}
}
2. 两数相加——链表——二刷
https://leetcode.cn/problems/add-two-numbers/?envType=study-plan-v2&envId=top-100-liked
思路:分成几个部分,分别实现,然后拼接在一起
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {// 1. 定义头结点ListNode pre = new ListNode(-1);ListNode res = pre;// 2. 两个链表共长部分,相加int temp = 0;while (l1 != null && l2 != null) {int val = (l1.val + l2.val + temp) % 10;temp = (l1.val + l2.val + temp) / 10;ListNode n = new ListNode(val);pre.next = n;pre = pre.next;l1 = l1.next;l2 = l2.next;}// 3. 两个链表各自其他部分,相加while (l1 != null) {ListNode n1 = new ListNode((l1.val + temp) % 10);temp = (l1.val + temp) / 10;pre.next = n1;pre = pre.next;l1 = l1.next;}while (l2 != null) {ListNode n2 = new ListNode((l2.val + temp) % 10);temp = (l2.val + temp) / 10;pre.next = n2;pre = pre.next;l2 = l2.next;}// 4. temp处理if (temp != 0) {ListNode n3 = new ListNode(temp);pre.next = n3;}return res.next;}
}
二刷:更丝滑,直接秒杀, 类似大数+链表合并的题型
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, w next) { this.val = val; this.next = next; }* }*/class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {// 解题思路,有点类似链表合并,注意最后进位的赋值ListNode l3 = new ListNode(-1);ListNode res = l3;int upNum = 0;while (l1 != null || l2 != null) {// 值处理int v1 = l1 == null ? 0 : l1.val;int v2 = l2 == null ? 0 : l2.val;int v = (v1 + v2 + upNum) % 10;upNum = (v1 + v2 + upNum) / 10;// 新节点声明ListNode newNode = new ListNode(v);l3.next = newNode;l3 = l3.next;// 节点遍历if (l1 != null) {l1 = l1.next;}if (l2 != null) {l2 = l2.next;}}// 进位处理if (upNum > 0) {ListNode newNode = new ListNode(upNum);l3.next = newNode;}return res.next;}
}
3. 无重复字符的最长子串——滑动窗口——三刷
模板
//外层循环扩展右边界,内层循环扩展左边界
for (int l = 0, r = 0 ; r < n ; r++) {//当前考虑的元素while (l <= r && check()) {//区间[left,right]不符合题意//扩展左边界}//区间[left,right]符合题意,统计相关信息
}
一刷代码
class Solution {public int lengthOfLongestSubstring(String s) {//滑动窗口char[] ss = s.toCharArray();Set<Character> set = new HashSet<>();//去重int res = 0;//结果for(int left = 0, right = 0; right < s.length(); right++) {//每一轮右端点都扩一个。char ch = ss[right];//right指向的元素,也是当前要考虑的元素while(set.contains(ch)) {//set中有ch,则缩短左边界,同时从set集合出元素set.remove(ss[left]);left++;}set.add(ss[right]);//别忘。将当前元素加入。res = Math.max(res, right - left + 1);//计算当前不重复子串的长度。}return res;}
}
二刷代码(20241006):
class Solution {public int lengthOfLongestSubstring(String s) {// 解题思路:指针滑动,不重复则加入, 重复则去掉第一个元素, 一直去,直到不重复为止int res = 0;char[] ss = s.toCharArray();int len = ss.length;Set<Character> set = new HashSet<>();for (int left = 0, right = 0; right < len; right++) {// 当前要处理的值char c = ss[right];// left滑动的条件while (left < right && set.contains(c)) {set.remove(ss[left]);left++;}set.add(c);res = Math.max(res, right - left + 1);}return res;}
}
三刷代码:
class Solution {public int lengthOfLongestSubstring(String s) {int len = s.length();if (len == 0) {return 0;}char[] ss = s.toCharArray();Set<Character> isContain = new HashSet<>();int res = 0;for (int l = 0, r = 0; r < len; r++) {char c = ss[r];while (l <= r && isContain.contains(c)) {isContain.remove(ss[l]);l++;}res = Math.max(res, r - l + 1);isContain.add(c);}return res;}
}
5.最长回文串——数组
https://leetcode.cn/problems/longest-palindromic-substring/description/?envType=study-plan-v2&envId=top-100-liked
给你一个字符串 s,找到 s 中最长的回文串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
思路分析:脑海中的第一解法一定是纯暴力解法,即双重for循环遍历,i>j,第三重循环里对从i到j的数据判断,是否符合回文串,记录最大值即可
时间复杂度:On^3
但是这样显然不优雅, 而且没有用到回文串本身的特性来帮助解题
第二种思路是:中心扩散法
即以某个下标为中心,向两侧扩散,记录符合最大长度的回文串即可
这里需要注意的是,回文串的判断分两部分,第一部分是中心相同字符的数量,第二部分是非中心两侧相等字符的数量, 需要拆开来算
class Solution {public static String longestPalindrome(String s) {// 入参校验if (s == null || s.length() == 0) {return null;}// 回文串遍历int len = s.length();int maxLeftIndex = 0;int maxRightIndex = 0;for (int i = 0; i < len; i++) {// 声明左右遍历下标int leftIndex = i;int rightIndex = i;// 中心区域处理-左遍历for (int j = leftIndex; j >= 0; j--) {if (s.charAt(leftIndex) != s.charAt(j)) {break;}leftIndex = j;}// 中心区域处理-右遍历for (int j = rightIndex; j < len; j++) {if (s.charAt(rightIndex) != s.charAt(j)) {break;}rightIndex = j;}// 回文区域处理while (s.charAt(leftIndex) == s.charAt(rightIndex)) {// 存储最大回文串下标if (rightIndex - leftIndex > maxRightIndex - maxLeftIndex) {maxLeftIndex = leftIndex;maxRightIndex = rightIndex;}leftIndex -= 1;rightIndex += 1;// 终止条件if (leftIndex < 0 || rightIndex >= len) {break;}}}return s.substring(maxLeftIndex, maxRightIndex + 1);}
}
二刷,代码更得心应手:
class Solution {public String longestPalindrome(String s) {String resStr = "";int resLen = 0;// for循环,对每个节点分为中心区域和回文区域,中心区域判断是否相等(偶数回文串)int len = s.length();char ss[] = s.toCharArray();for (int i = 0; i < len; i++) {// 中间区域处理int left = i, right = i;char c = ss[i];while (left >= 0 && ss[left] == c) {left--;}while (right < len && ss[right] == c) {right++;}// 回文区域处理while (left >= 0 && right < len && ss[left] == ss[right]) {left--; right++;}// 结果处理if (right - left + 1 > resLen) {resLen = right - left + 1;resStr = s.substring(left + 1, right);}}return resStr;}
}
三刷:直接秒了
class Solution {public String longestPalindrome(String s) {// 中心扩散法,主注意处理中心区域和回文区域int len = s.length();if (len <= 1) {return s;}char[] cc = s.toCharArray();int resLen = 1;String resStr = s.substring(0, 1);for (int i = 0; i < len; i++) {int left = i, right = i;int c = cc[i];while (left >= 0 && cc[left] == c) {left--;}while (right < len && cc[right] == c) {right++;}while (left >= 0 && right < len && cc[left] == cc[right]) {left--;right++;}if (resLen < right - left - 1) {resLen = right - left - 1;resStr = s.substring(left + 1, right);}}return resStr;}
}
11. 盛水最多的容器——双指针——二刷
https://leetcode.cn/problems/container-with-most-water/submissions/570462070/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码,也可以参考力扣题解
class Solution {public int maxArea(int[] height) {// 核心思想:假设x位于最左侧, y位于最右侧, 储水面积取决于较短的柱子长度*二者距离, 假设固定较短的柱子x,y向左移动,那么储水面积一定没有原来大, 但是假设固定住较长的柱子y,x向右移动,那么储水面积可能比原来大,也可能比原来小, 每一步都这么处理,最后就能求出最大的储水面积int maxArea = -1;int i = 0, j = height.length - 1;while (i < j) {maxArea = Math.max(maxArea, (j - i) * Math.min(height[i], height[j]));if (height[i] > height[j]) {j--;} else {i++;}}return maxArea;}
}
二刷:贪心+双指针,秒了
class Solution {public int maxArea(int[] height) {// 遍历方向有两个,一是按照板子长度,二是按照坐标间隔,板子长度无法预知,只能从长倒短遍历坐标间隔// 然后,每次缩短长度,可以缩短左边一次,也可以缩短右边一次,取决于二者之间的最大值。// 贪心的原因,假如左边板子长度小于右边板子长度, 如果选择固定左边板子,则剩余所有遍历,高度一定不会高于左边板子,而如果固定右边板子,则剩余所有遍历,高度一定不会高于右边板子, 右边板子高于左边,所以保留右边int len = height.length;int i = 0, j = len - 1;int res = (j - i) * Math.min(height[i], height[j]);while (i < j) {if (height[i] < height[j]) {i++;} else {j--;}res = Math.max(res, (j - i) * Math.min(height[i], height[j]));}return res;}
}
15. 三数之和——数组——二刷
https://leetcode.cn/problems/3sum/?envType=study-plan-v2&envId=top-100-liked
一刷,时间:20241006
class Solution {public List<List<Integer>> threeSum(int[] nums) {Arrays.sort(nums);// 1. 保证下标没有重复(i < j < k)// 2. 保证数字集合不重复(Set)List<List<Integer>> res = new ArrayList<>();Set<List<Integer>> isRepeat = new HashSet<>();Map<Integer, List<Integer>> isContain = new HashMap<>();int len = nums.length;for (int i = 0; i < len; i++) {List list;if (isContain.containsKey(nums[i])) {list =isContain.get(nums[i]);} else {list = new ArrayList<>();}list.add(i);isContain.put(nums[i], list);}for (int i = 0; i < len; i++) {for (int j = i + 1; j < len; j++) {int sum = nums[i] + nums[j];if (isContain.containsKey(-sum) && isContain.get(-sum).getLast() > j) {List<Integer> cur = new ArrayList<>(Arrays.asList(nums[i], nums[j], -sum));if (!isRepeat.contains(cur)) {res.add(cur);isRepeat.add(cur);}}}}return res;}
}
二刷时间:20241015
排序那里处理的还是有点问题,其他的比较流利
其实更优解,是初始排序, 这样时间复杂度更低
class Solution {public List<List<Integer>> threeSum(int[] nums) {// 核心思路,固定一个,另外两个按照两数之和来求,保证ijk不重复List<List<Integer>> res = new ArrayList<>();Set<List<Integer>> isContain = new HashSet<>();int len = nums.length;for (int i = 0; i < len; i++) {int n1 = nums[i];Set<Integer> set = new HashSet<>();for (int j = i + 1; j < len; j++) {int n2 = nums[j];int n3 = 0 - n1 - n2;if (set.contains(n3)) {int[] list = new int[] {n1, n2, n3};Arrays.sort(list);List<Integer> tempList = new ArrayList<>();tempList.add(list[0]); tempList.add(list[1]); tempList.add(list[2]);if (!isContain.contains(tempList)) {res.add(tempList);isContain.add(tempList);}} else {set.add(n2);}}}return res;}
}
19. 删除链表的倒数第n个节点——链表
https://leetcode.cn/problems/remove-nth-node-from-end-of-list/?envType=study-plan-v2&envId=top-100-liked
- 定义哑结点
- 注意边界场景,最好先根据极限场景手推一遍
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {// 1. 哑结点ListNode pre = new ListNode(-1);pre.next = head;// 2. second节点,先走n-1步(n-1是根据极限情况推出来的)ListNode first = pre, second = head;for (int i = 0; i < n-1; i++) {second = second.next;}// 3. First和second同时走,直到second遍历到最后一位while (second.next != null) {second = second.next;first = first.next;}// 4. 直接删除对应节点即可first.next = first.next.next;return pre.next;}
}
二刷:直接秒,这时已经做了不少链表题了,写的很舒服
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {// 这是预处理链表长度的解法, 还有一种是双指针,指针2先走n步,指针2到尾部时,指针1刚好到倒数第n个位置,删除即可if (head == null) {return null;}// 1. 求长度int len = 0;ListNode h1 = head;while (h1 != null) {len++;h1 = h1.next;}// 2. 删除:定义哑结点,删除后直接break即可。ListNode n1 = new ListNode(-1), n2 = head;n1.next = head;ListNode res = n1;int target = len - n + 1;int cur = 1;while (n2 != null) {if (cur == target) {n1.next = n2.next;n2.next = null;break;}n1 = n1.next;n2 = n2.next;cur++;}return res.next;}
}
20. 有效的括号——栈——二刷
https://leetcode.cn/problems/valid-parentheses/?envType=study-plan-v2&envId=top-100-liked
解题思路:栈进左括号, 如果遇到右括号,就依次出栈, 如果中途栈空,或不匹配,或还剩右括号,都属于false
class Solution {public boolean isValid(String s) {List<Character> stack = new LinkedList<Character>();Map<Character, Character> map = new HashMap<Character, Character>();map.put('(', ')');map.put('[', ']');map.put('{', '}');char[] ss = s.toCharArray();for (char c : ss) {if (map.containsKey(c)) {// 左括号入栈stack.addFirst(c);} else {if (stack.isEmpty()) {return false;}char rc = stack.removeFirst();if (map.get(rc) != c) {return false;}}}return stack.isEmpty();}
}
二刷:直接秒
class Solution {public boolean isValid(String s) {// 思路 :如果是左括号,全部存入栈,如果是右括号,从栈中取一个数据,如果不匹配,或栈里有数据,false, 最后如果栈没有被清空,falseList<Character> stack = new LinkedList<>();Map<Character, Character> map = new HashMap<>();map.put('(', ')');map.put('[', ']');map.put('{', '}');char[] cc = s.toCharArray();for (Character c : cc) {if (c == '(' || c == '[' || c == '{') {stack.addFirst(c);} else {if (stack.isEmpty()) {return false;}Character stackChar = stack.removeFirst();if (c != map.get(stackChar)) {return false;}}}if (!stack.isEmpty()) {return false;}return true;}
}
21. 合并两个有序链表——链表-二刷
https://leetcode.cn/problems/merge-two-sorted-lists/description/?envType=study-plan-v2&envId=top-100-liked
核心思路:
第一步,构造一个哑结点,从头声明结果链表,循环两个链表,哪个小就放入哪个
第二步,如果某个循环完了,另一个还没完,那直接把另一个剩下的节点,接到结果链表后面即可。
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode pre = new ListNode(-1);ListNode res = pre;while (list1 != null && list2 != null) {if (list1.val < list2.val) {pre.next = list1;list1 = list1.next;} else {pre.next = list2;list2 = list2.next;}pre = pre.next;}if (list1 != null) {pre.next = list1;} else {pre.next = list2;}return res.next;}
}
二刷,秒了
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {if (list1 == null && list2 == null) {return null;}ListNode newList = new ListNode(-1);ListNode res = newList;// 两个链表都有值的之后,需要比较while (list1 != null && list2 != null) {int val = -1;if (list1.val < list2.val) {val = list1.val;list1 = list1.next;} else {val = list2.val;list2 = list2.next;}ListNode newNode = new ListNode(val);newList.next = newNode;newList = newList.next;}// 两个链表各自剩下的部分if (list1 != null) {newList.next = list1;}if (list2 != null) {newList.next = list2;}return res.next;}
}
22. 括号生成——DFS&回溯*
https://leetcode.cn/problems/generate-parentheses/?envType=study-plan-v2&envId=top-100-liked
解题思路:核心限制是, 某个位置如果写入右括号,则必须满足该位置之前的左括号数量 > 右括号数量, 用两个变量做标记即可。
class Solution {// 全局变量声明int maxl = 0, maxr = 0;String s = "";List<String> res = new ArrayList<>();public List<String> generateParenthesis(int n) {dfs(n);return res;}public void dfs (int n) {// 终值条件if (maxl == n && maxr == n) {res.add(new String(s));return;}// 左括号if (maxl < n) {s += "(";maxl += 1;dfs(n);s = s.substring(0, s.length() - 1);maxl -= 1;}// 右括号if (maxr < n && maxr < maxl) {s += ")";maxr += 1;dfs(n);s = s.substring(0, s.length() - 1);maxr -= 1;}}
}
23. 合并k个升序链表——链表&归并——二刷
https://leetcode.cn/problems/merge-k-sorted-lists/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeKLists(ListNode[] lists) {// 归并 + 链表合并的裸题 // 暴力合并,时间复杂度 k * kn,其中,k是每轮遍历的个数,kn是节点个数// 归并合并,时间复杂度 logk * kn// 整体思路就是两两归并,最后返回一个有序的链表if (lists.length == 0) {return null;}return merge(lists, 0, lists.length - 1);}public ListNode merge(ListNode[] lists, int l, int r) {// 递归抽象不好理解, 这里可以思考如果List中只有1个或2个或3个链表,即只递归0次或1次,整体怎么运转和设计。if (l == r) {return lists[l];}if (l > r) {return null;}int mid = (l + r) >> 1;ListNode n1 = merge(lists, l, mid);ListNode n2 = merge(lists, mid + 1, r);return mergeTwoLink(n1, n2);}public ListNode mergeTwoLink(ListNode n1, ListNode n2) {if (n1 == null && n2 == null) {return null;}ListNode n3 = new ListNode(-1);ListNode res = n3;while (n1 != null && n2 != null) {int v = 0;if (n1.val < n2.val) {v = n1.val;n1 = n1.next;} else {v = n2.val;n2 = n2.next;}ListNode newNode = new ListNode(v);n3.next = newNode;n3 = n3.next;}if (n1 != null) {n3.next = n1;}if (n2 != null) {n3.next = n2;}return res.next;}}
二刷,直接秒了,丝滑
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode mergeKLists(ListNode[] lists) {// 归并 + 合并,时间复杂度 m*n*logkif (lists.length == 0) {return null;}return merge(lists, 0, lists.length - 1);}public ListNode merge(ListNode[] lists, int l, int r) {if (l > r) {return null;}if (l == r) {return lists[l];}int mid = (l + r) >> 1;ListNode n1 = merge(lists, l, mid);ListNode n2 = merge(lists, mid + 1, r);return mergeTwoList(n1, n2);}public ListNode mergeTwoList(ListNode n1, ListNode n2) {if (n1 == null && n2 == null) {return null;}ListNode n3 = new ListNode(-1);ListNode res = n3;while (n1 != null && n2 != null) {int val = 0;if (n1.val < n2.val) {val = n1.val;n1 = n1.next;} else {val = n2.val;n2 = n2.next;}ListNode newNode = new ListNode(val);n3. next = newNode;n3 = n3.next;}if (n1 != null) {n3.next = n1;}if (n2 != null) {n3.next = n2;}return res.next;}
}
24. 两两交换链表中的节点——链表——二刷
https://leetcode.cn/problems/s+wap-nodes-in-pairs/?envType=study-plan-v2&envId=top-100-liked
解题思路:定义哑结点,然后置换即可。
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode swapPairs(ListNode head) {if (head == null) {return null;}ListNode pre = new ListNode(-1);pre.next = head;ListNode res = pre;while (pre.next != null && pre.next.next != null) {ListNode n1 = pre, n2 = pre.next, n3 = pre.next.next;\n2.next = n3.next;n3.next = n2;n1.next = n3;pre = pre.next.next;}return res.next;}
}
二刷:完全不用定义哑结点,直接干,秒杀题,比较丝滑
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode swapPairs(ListNode head) {if (head == null || head.next == null) {return head;}ListNode n1 = head, n2 = head.next;while (n2 != null) {int t = n1.val;n1.val = n2.val;n2.val = t;if (n2.next != null) {n1 = n1.next.next;n2 = n2.next.next;} else {break;}}return head;}
}
25. k个一组翻转链表——链表*——一刷
https://leetcode.cn/problems/reverse-nodes-in-k-group/?envType=study-plan-v2&envId=top-100-liked
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode reverseKGroup(ListNode head, int k) {// 解题思路:// 1. 保存每一组的前缀节点// 2. 循环获取每一组的范围,如果遍历到链表末尾,则直接break// 3. 保存下一组的头节点, 并切断与当前组的联系// 4. 反转当前组, 然后当前组的头尾节点分别重新连接进链表// 5. 更新前缀节点if (head == null || head.next == null || k == 1) {return head;}// 哑结点ListNode dummy = new ListNode(-1);dummy.next = head;// 每一组的前缀节点ListNode preGroup = dummy;while(true) {// 每组的起始节点和结束节点ListNode start = preGroup.next;ListNode end = start;for (int i = 0; i < k - 1 && end != null; i++) {end = end.next;}// 终止条件if (end == null) {break;}// 记录下一组的开始节点,并断裂与下一组的联系ListNode nextStart = end.next;end.next = null;// 反转当前数组ListNode curGroupEnd = reserve(start);// 连接preGroup.next = curGroupEnd;start.next = nextStart;// 更新前缀节点preGroup = start;}return dummy.next;}public ListNode reserve(ListNode head) {ListNode n1 = null, n2 = head, n3 = head.next;while (n2 != null) {n2.next = n1;if (n3 == null) {break;}n1 = n2;n2 = n3;n3 = n3.next;}return n2;}
}
27. 移除元素——双指针——一刷
https://leetcode.cn/problems/remove-element/
class Solution {public int removeElement(int[] nums, int val) {// 解题思路:双指针,确保左指针左侧全部不等于value,最后返回长度即可int len = nums.length;if (len == 0) {return 0;}int i = 0, j = 0;while (i <= j && j < len) {if (nums[j] != val) {nums[i] = nums[j];i++;}j++;}return i;}
}
33. 搜索旋转排序数组——二分*
解题思路:对于二分出来的左子数组和右子数组, 先判断出哪个数组是连续的(另一个数组必然不连续),然后在连续的数组内,根据边界条件判断目标元素是否在其中,如果不在其中, 那无脑遍历另一半数组, 一直继续即可。
注意:
- 边界条件的处理:
nums[0] <= nums[mid]
,为什么要加等于号, 如果左半区间只有一个数组,那么是等于的, 一个数组理论上也是有序的。 target < nums[mid] && target >= nums[0]
:对于左半区间的判断,为什么要判断开始和结束,而不是只判断mid, 因为有可能是翻转数组,比如target是1, 数组是4567123,那么nums[mid]也是小于target的, 但是target并不在左半区间
class Solution {public int search(int[] nums, int target) {int l = 0, r = nums.length - 1;int res = -1;while (l <= r) {int mid = (l + r) >> 1;if (nums[mid] == target) {return mid;}if (nums[0] <= nums[mid]) {if (target < nums[mid] && target >= nums[0]) {r = mid - 1;} else {l = mid + 1;}} else {if (target <= nums[r] && target > nums[mid]) {l = mid + 1;} else {r = mid - 1;}}}return res;}
}
二刷:直接秒杀,思路理清很简单
class Solution {public int search(int[] nums, int target) {// 思路:数组二分, 必定一段有序一段无序, 因为转折点只有一个; 这样只需要判断有序的那边,是不是存在target,如果不存在,无脑转另一边,如果存在,则继续缩减即可。int len = nums.length;int l = 0, r = len - 1;while (l <= r) {int mid = (l + r) >> 1;if (target == nums[mid]) {return mid;}// 这里要注意,等于说明只有一个元素,只有一个元素也算有序if (nums[l] <= nums[mid]) {if (target <= nums[mid] && target >= nums[l]) {r = mid - 1;} else {l = mid + 1;}} else {if (target >= nums[mid] && target <= nums[r]) {l = mid + 1;} else {r = mid - 1;}}}return -1;}
}
34. 在排序数组中查找元素的第一个和最后一个位置——二分——二刷
https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solutions/504484/zai-pai-xu-shu-zu-zhong-cha-zhao-yuan-su-de-di-3-4/?envType=study-plan-v2&envId=top-100-liked
思路:两次二分, 第一次是大于等于, 第二次是小于等于, 找左边界和右边界,当等于的时候, 赋值
class Solution {public int[] searchRange(int[] nums, int target) {int n = nums.length;int[] res = new int[2];int left = -1, right = -1;int l = 0, r = n - 1;while (l <= r) {int mid = (l + r) >> 1;if (nums[mid] >= target) {if (nums[mid] == target) {left = mid;}r = mid - 1;} else {l = mid + 1;}}l = 0; r = n - 1;while (l <= r) {int mid = (l + r) >> 1;if (nums[mid] <= target) {if (nums[mid] == target) {right = mid;}l = mid + 1;} else {r = mid - 1;}}res[0] = left;res[1] = right;return res;}
}
二刷:比较丝滑
class Solution {public int[] searchRange(int[] nums, int target) {int[] res = new int[] {-1, -1};// 这里就是先偏向左边还是先偏向右边即可int len = nums.length;int l1 = 0, r1 = len - 1;while (l1 <= r1) {int mid1 = (l1 + r1) >> 1;if (target <= nums[mid1]) {if (target == nums[mid1]) {res[0] = mid1;}r1 = mid1 - 1;} else {l1 = mid1 + 1;}}int l2 = 0, r2 = len - 1;while (l2 <= r2) {int mid2 = (l2 + r2) >> 1;if (target >= nums[mid2]) {if (target == nums[mid2]) {res[1] = mid2;}l2 = mid2 + 1;} else {r2 = mid2 - 1;}}return res;}
}
35. 搜索插入位置——二分——二刷
https://leetcode.cn/problems/search-insert-position/description/?envType=study-plan-v2&envId=top-100-liked
class Solution {public int searchInsert(int[] nums, int target) {int l = 0, r = nums.length-1;while(l <= r) {int mid = (l + r) >> 1;if (nums[mid] == target) {return mid;}if (nums[mid] > target) {r = mid - 1;} else {l = mid + 1;}}return l;}
}
41. 缺失的第一个正数——数组——二刷
https://leetcode.cn/problems/first-missing-positive/?envType=study-plan-v2&envId=top-100-liked
思想:替换,因为缺失的正数一定在1-n之间,所以直接替换,让下标=值,然后遍历, 第一个值不等于下标的, 一定是数组中没有的最小正整数
class Solution {public int firstMissingPositive(int[] nums) {int len = nums.length;for (int i = 0; i < len; i++) {while (nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]) {int t = nums[nums[i] - 1];nums[nums[i] - 1] = nums[i];nums[i] = t;}}for (int i = 0; i < len; i++) {if (nums[i] != i + 1) {return i + 1;}}return len+1;}
}
二刷,卡了两个用例,改了改代码过了,整体看更娴熟了
class Solution {public int firstMissingPositive(int[] nums) {int len = nums.length;for (int i = 0; i < len; i++) {// while循环放置位置while (nums[i] != i + 1 && nums[i] > 0 && nums[i] < len) {// 终止条件, 相同就别换了, 换就死循环if (nums[i] == nums[nums[i] - 1]) {break;}swap(nums, i, nums[i] - 1);}}for (int i = 0; i < len; i++) {if (nums[i] != i + 1) {return i + 1;}}return len + 1;}public void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}
45. 跳跃游戏二——贪心*
https://leetcode.cn/problems/jump-game-ii/?envType=study-plan-v2&envId=top-100-liked
解题思路:见注释,很巧妙
class Solution {public int jump(int[] nums) {int len = nums.length;// 特判if (len == 1) {return 0;}int res = 0; int tempEndPos = 0; int maxPos = 0;for (int i = 0; i < len; i++) {// 贪心策略,获取每一步的最优解if (i + nums[i] > maxPos) {maxPos = i + nums[i];}// 当i遍历到上一步的最优解后, 把值更新到下一步的最优解if (i == tempEndPos) {res++;tempEndPos = maxPos;// 判定if (maxPos >= len - 1) {return res;}}}return res;}
}
46. 全排列——回溯&DFS——二刷
地址:https://leetcode.cn/problems/permutations/?envType=study-plan-v2&envId=top-100-liked
解题思路:第一个位置有n种变化,第二个位置有n-1种变化… 以此类推,用数组标记变换即可。
class Solution {int[] flag;public List<List<Integer>> permute(int[] nums) {List<List<Integer>> res = new ArrayList<>();List<Integer> cur = new ArrayList<Integer>();for (int i = 0; i < nums.length; i++) {cur.add(nums[i]);}dfs(res, cur, 0, nums.length); return res;}public void dfs(List<List<Integer>> res, List<Integer> cur, Integer first, Integer len) {if (first == len) {// 这里要注意, 一定要重新声明地址, 否则更改的是同一个位置res.add(new ArrayList<Integer>(cur));}for (int i = first; i < len; i++) {swap(cur, i, first);dfs(res, cur, first + 1, len);swap(cur, i, first);}}public void swap(List<Integer> cur, Integer i, Integer j) {int t = cur.get(i);cur.set(i, cur.get(j));cur.set(j, t);}
}
二刷:debug了,整体还是有点不熟练
class Solution {List<List<Integer>> res = new ArrayList<>();Set<Integer> flag = new HashSet<>();public List<List<Integer>> permute(int[] nums) {int len = nums.length;List<Integer> curList = new ArrayList<>();dfs(curList, nums, len);return res;}public void dfs(List<Integer> curList, int[] nums, int len) {int curLen = curList.size();if (curLen == len) {res.add(new ArrayList<>(curList));return;}for (int i = 0; i < len; i++) {int t = nums[i];if (!flag.contains(t)) {curList.add(t);flag.add(t);dfs(curList, nums, len);curList.remove(curLen);flag.remove(t);}}}
}
48. 旋转图像——矩阵——一刷
https://leetcode.cn/problems/rotate-image/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码
class Solution {public void rotate(int[][] matrix) {// 思路:观察可得,旋转90度后,第1行的数据,会被作为最后一列, 此时考虑对角线翻折,但是如果用对角线,第i行的第一个数据,会被放在n-i列的最后一位,但是旋转后,应该是放在第一位的, 那此时,再对矩阵进行水平翻转,即可。int len = matrix.length;// 对角线翻转for (int i = 0; i < len; i++) {for (int j = 0; j < i; j++) {int t = matrix[i][j];matrix[i][j] = matrix[j][i];matrix[j][i] = t;}}// 垂直翻转for (int i = 0; i < len; i++) {for (int j = 0; j < len / 2; j++) {int t = matrix[i][j];matrix[i][j] = matrix[i][len - j - 1];matrix[i][len - j - 1] = t;}}}
}
49. 字母异位词分组——哈希——二刷
https://leetcode.cn/problems/group-anagrams/?envType=study-plan-v2&envId=top-100-liked
解题思路:水题直接秒
class Solution {public List<List<String>> groupAnagrams(String[] strs) {List<List<String>> res = new ArrayList<>();Map<String, List<String>> map = new HashMap<>();for (String str : strs) {// 排序确保唯一性char[] ss = str.toCharArray();Arrays.sort(ss);String newStr = new String(ss);List list;if (!map.containsKey(newStr)) {list = new ArrayList<String>();} else {list = map.get(newStr);}list.add(str);map.put(newStr, list);}for (List<String> strList : map.values()) {res.add(strList);}return res;}
}
二刷:
class Solution {public List<List<String>> groupAnagrams(String[] strs) {// 核心思路:排序后的String做key,分组即可Map<String, List<String>> map = new HashMap<>();for (String str : strs) {char[] cc = str.toCharArray();Arrays.sort(cc);String orderStr = new String(cc);if (map.containsKey(orderStr)) {List<String> list = map.get(orderStr);list.add(str);map.put(orderStr, list);} else {List<String> list = new ArrayList<>();list.add(str);map.put(orderStr, list);}}List<List<String>> res = new ArrayList<>();for (List<String> value : map.values()) {res.add(value);}return res;}
}
53. 最大子数组和——贪心
https://leetcode.cn/problems/maximum-subarray/solutions/228009/zui-da-zi-xu-he-by-leetcode-solution/?envType=study-plan-v2&envId=top-100-liked
思想:贪心,如果子数组和大于0就继续贪,如果小于0 ,就从0开始继续贪
class Solution {public int maxSubArray(int[] nums) {int maxNums = -100000;int tMaxNums = 0;for (int i = 0; i < nums.length; i++) {tMaxNums += nums[i];if (tMaxNums > maxNums) {maxNums = tMaxNums;}if (tMaxNums < 0) {tMaxNums = 0;}}return maxNums;}
}
二刷:细节处理要注意
class Solution {public int maxSubArray(int[] nums) {int res = -Integer.MAX_VALUE;int tmp = 0;for (int i = 0; i < nums.length; i++) {tmp += nums[i];res = Math.max(res, tmp);// 顺序注意一下if (tmp < 0) {tmp = 0;}}return res;}
}
54. 螺旋矩阵——矩阵——二刷
https://leetcode.cn/problems/spiral-matrix/?envType=study-plan-v2&envId=top-100-liked
解题思路:硬写怎么都能写出来, 但是如果有限时间场景, 就要有良好的设计和结构, 如下所示, 定义上下左右四个变量, 每个边遍历一次就缩减一次,直到上下边界相交或左右边界相交。
class Solution {public List<Integer> spiralOrder(int[][] matrix) {List<Integer> res = new ArrayList<>();int m = matrix.length, n = matrix[0].length;int left = 0, right = n - 1, top = 0, bottom = m - 1;while (left <= right && top <= bottom) {for (int i = left; i <= right; i++) {res.add(matrix[top][i]);}top++;for (int i = top; i <= bottom; i++) {res.add(matrix[i][right]);}right--;// 为什么这两个要if,上面两个不要if,因为上面两个在while处判断过了if (top <= bottom) {for (int i = right; i >= left; i--) {res.add(matrix[bottom][i]);}bottom--;}if (left <= right) {for (int i = bottom; i >= top; i--) {res.add(matrix[i][left]);}left++;}}return res;}
}
二刷,基本算秒杀,但是调试了一下,做了很多题脑力跟不上了, 正常应该能解出的, 还可以。
class Solution {public List<Integer> spiralOrder(int[][] matrix) {// 核心是良好的代码结构int lenx = matrix.length, leny = matrix[0].length;int top = 0, bottom = lenx - 1, left = 0, right = leny - 1;List<Integer> res = new ArrayList<>();while (left <= right && top <= bottom) {for (int i = left; i <= right; i++) {res.add(matrix[top][i]);}top++;for (int i = top; i <= bottom; i++) {res.add(matrix[i][right]);}right--;if (top <= bottom) {for (int i = right; i >= left; i--) {res.add(matrix[bottom][i]);}bottom--;}if (left <= right) {for (int i = bottom; i >= top; i--) {res.add(matrix[i][left]);}left++;}}return res;}
}
55. 跳跃游戏——贪心——二刷
https://leetcode.cn/problems/jump-game/?envType=study-plan-v2&envId=top-100-liked
贪心核心思路:当前最优策略,然后对比新一轮的最优策略,是否优于之前记录的, 如果是,则替换
本题思路:第i轮最优策略, 在于前i轮能走出的最大步数; 同时, 如果i的遍历,超过了能走的最大步数,说明不可达,返回false; 如果最大步数,大于等于数组长度, 说明可达,返回true
class Solution {public boolean canJump(int[] nums) {int len = nums.length;// 记录前i轮能走出的最大步数int curLen = 0;for (int i = 0; i < len; i++) {// 如果i > 能走出的最大步数,说明不可达if (i > curLen) {return false;}// 对比每一轮的 步长,如果超过当前最大步数,则说明是更优解, 替换if (i + nums[i] > curLen) {curLen = i + nums[i];}// 可达场景if (curLen >= len - 1) {return true;}}return false;}
}
二刷,想了想思路,秒掉的
class Solution {public boolean canJump(int[] nums) {// 记录一个目前为止能走到的最大步数,即可int len = nums.length;int maxNum = 0;for (int i = 0; i < len; i++) {// 断路if (maxNum < i) {return false;}// 记录最大值maxNum = Math.max(i + nums[i], maxNum);// 可通过if (maxNum >= len - 1) {return true;}}return true;}
}
56. 合并区间——数组*——二刷
https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&envId=top-100-liked
解题思路:二维数组的遍历,感觉思路不难,更难的是二维数组的声明,自定义排序这些, 因为平时Idea写代码,直接copy了,很少能背下来
class Solution {public int[][] merge(int[][] intervals) {// 1. 排序Arrays.sort(intervals, Comparator.comparingInt(interval -> interval[0]));int n = intervals.length;int i = 0;List<int[]> ranges = new ArrayList<>(n);// 2. 遍历 + 合并while(i < n) {int start = intervals[i][0], end = intervals[i][1];int j = i + 1;while (j < n && end >= intervals[j][0]) {end = Math.max(end, intervals[j][1]);j++;}ranges.add(new int[] {start, end});i = j;}// 3. 动态数组转二维数组int resLen = ranges.size();int[][] res = new int[resLen][2];for (int t = 0; t < resLen; t++) {res[t] = ranges.get(t);}return res;}
}
二刷,细节处理要注意,右区间的边界赋值要注意。 可以定义一个start和end,动态替换
class Solution {public int[][] merge(int[][] intervals) {Arrays.sort(intervals, Comparator.comparingInt(interval -> interval[0]));List<int[]> list = new ArrayList<>(); int len = intervals.length;int i = 0;while (i < len) {int start = intervals[i][0], end = intervals[i][1];int j = i + 1;while (j < len) {if (end >= intervals[j][0]) {// 核心代码,两个区间合并后,右区间值取决于两个区间中,右区间的较大者end = Math.max(end, intervals[j][1]);j++;} else {break;}}int[] tmpNums = new int[] {start, end};list.add(tmpNums);i = j;}int[][] res = new int[list.size()][2];for (i = 0; i < list.size(); i++) {res[i] = list.get(i);}return res;}
}
70. 爬楼梯——DP——二刷
https://leetcode.cn/problems/climbing-stairs/submissions/570587960/?envType=study-plan-v2&envId=top-100-liked
解题思路:基础动态规划
class Solution {public int climbStairs(int n) {// 转移方程: fn = fn-1 + fn-2;// 边界条件: f0 = 1; f1 = 1; f2 = 2int p = 0, q = 0, r = 1;for (int i = 0; i < n; i++) {p = q; // fn-2q = r; // fn-1r = p + q; // fn}return r;}
}
二刷:两个变量解决,直接秒
class Solution {public int climbStairs(int n) {// 边界条件n=1, res = 1;// 边界条件n=2, res = 2;// n = 3时, 总步数 = (n为2时种类数) + (n为1时种类数),即fn = fn-1 + fn-2if (n == 1) {return 1;} else if (n == 2) {return 2;}int n1 = 1, n2 = 2;for (int i = 3; i <= n; i++) {if (n1 < n2) {n1 = n1 + n2;} else {n2 = n1 + n2;}}return Math.max(n1, n2);}
}
72. 编辑距离——二维DP——二刷
https://leetcode.cn/problems/edit-distance/?envType=study-plan-v2&envId=top-100-liked
解题思路:参考题解和代码
class Solution {public int minDistance(String word1, String word2) {// 问题一:为什么只在末尾插入和修改? 因为本质上在中间或末尾修改不影响最终的操作次数// 解题思路:// 1. 对a串删除数据,可以看做是对b串增加数据, 这样三种操作就转换为了:// 对a串增加数据, 对b串增加数据, 修改a串的值// 2. 理论上如果要查询 abc 和 d的最最小距离, 只需要查询ab和d的最小距离 + 1即可, 因此问题就可以// 转化为基于边界条件开始的遍历// 3. 边界条件, a串为abc,b串为空的前提下, a串和b串相同的最短距离就是3, 依次类推,此为边界条件// 公式:// 如果a串和b串最后一个字符相同,则dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1])// 如果a串和b串最后一个字符不同,则dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+1)int len1 = word1.length(), len2 = word2.length();char[] ss1 = word1.toCharArray(), ss2 = word2.toCharArray();int dp[][] = new int[len1+1][len2+1];// 边界条件for (int i = 0; i <= len1; i++) {dp[i][0] = i;}for (int j = 0; j <= len2; j++) {dp[0][j] = j;}for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {int d1 = dp[i-1][j] + 1;int d2 = dp[i][j-1] + 1;int d3 = dp[i-1][j-1];if (ss1[i-1] != ss2[j-1]) {d3 += 1;}dp[i][j] = Math.min(d1, Math.min(d2, d3));}}return dp[len1][len2];}
}
二刷, 还是看了思路,但是实现更丝滑
class Solution {public int minDistance(String word1, String word2) {// 1. 对a串删除 = 对b串增加, 因此操作转换为: 对a串增加、对b串增加、对a串或b串修改// 2. 边界条件的构造: 例如a串为abc,b串为空,a经过3步可以变为b// 3. 递推公式:如果需要查询abc到c的最小距离, 只需要查询ab到b的最短距离 + 1; 如果要查询ab到b的最短距离,只需查询a到b的最短距离 + 1// 如果二者最后一个字母相同, 则dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1])// 如果二者最后一个字母不同, 则dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+1)char[] cc1 = word1.toCharArray(), cc2 = word2.toCharArray();int len1 = cc1.length, len2 = cc2.length;int[][] dp = new int[len1 + 1][len2 + 1];if (len1 == 0 && len2 == 0) {return 0;}if (len1 == 0) {return len2;}if (len2 == 0) {return len1;}for (int i = 0; i <= len1; i++) {dp[i][0] = i;}for (int i = 0; i <= len2; i++) {dp[0][i] = i;}for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {if (cc1[i - 1] == cc2[j - 1]) {dp[i][j] = Math.min(dp[i-1][j] + 1, Math.min(dp[i][j-1] + 1, dp[i-1][j-1]));} else {dp[i][j] = Math.min(dp[i-1][j] + 1, Math.min(dp[i][j-1] + 1, dp[i-1][j-1] + 1));}}}return dp[len1][len2];}
}
74. 搜索二维矩阵
https://leetcode.cn/problems/search-a-2d-matrix/?envType=study-plan-v2&envId=top-100-liked
解题思路:两次二分即可
注意命名的清晰, 以及边界的考虑, 比如第一次二分没有找到结果,直接返回就可以了
class Solution {public boolean searchMatrix(int[][] matrix, int target) {int xleft = 0, xright = matrix.length - 1, yleft = 0, yright = matrix[0].length - 1;int x = -1, y = -1;while (xleft <= xright) {int midx = (xleft + xright) >> 1;if (matrix[midx][yleft] <= target && matrix[midx][yright] >= target) {x = midx;break;} else if (target > matrix[midx][yright]) {xleft = midx + 1;} else if (target < matrix[midx][yleft]) {xright = midx - 1;}}if (x == -1) {return false;}while (yleft <= yright) {int midy = (yleft + yright) >> 1;if (matrix[x][midy] == target) {y = midy;break;} else if (target > matrix[x][midy]) {yleft = midy + 1;} else if (target < matrix[x][midy]) {yright = midy - 1;}}if (y == -1) {return false;}return true;}
}
75. 颜色分类——技巧*
https://leetcode.cn/problems/sort-colors/?envType=study-plan-v2&envId=top-100-liked
class Solution {public void sortColors(int[] nums) {// 技巧题,思考一下正常排序最低基本是Onlogn,这道题哪里可以优化? 答案是给定了只有三种数字// 定义左指针,左指针及其左边都是0(初始指向-1),定义右指针,右指针及其右边都是2(初始指向len),遍历一遍即可// 限定条件:l<i<rint len = nums.length;int l = -1, r = len;int i = 0;while(i < r) {if (nums[i] == 1) {i++;} else if (nums[i] == 0) {swap(nums, l + 1, i);l++;if (l >= i) {i = l + 1;}} else {swap(nums, r - 1, i);r--;}}}public void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}
78. 子集——DFS&回溯*
https://leetcode.cn/problems/subsets/?envType=study-plan-v2&envId=top-100-liked
解题思路:每个位置的元素,都有add 或 不add两种情况, 比如都add,就是所有元素的集合, 都不add,就是空集合, 把这个思想量化即可。
class Solution {List<List<Integer>> res = new ArrayList<>();List<Integer> curList = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {dfs(nums, 0);return res;}public void dfs(int[] nums, Integer curLen) {if (curLen == nums.length) {res.add(new ArrayList<Integer>(curList));return;}curList.add(nums[curLen]);dfs(nums, curLen + 1);curList.remove(curList.size() - 1);dfs(nums, curLen + 1);}
}
79. 单词搜索——DFS&回溯*
https://leetcode.cn/problems/word-search/submissions/570382049/?envType=study-plan-v2&envId=top-100-liked
解题思路:标记flag数组,记录哪些方向可以被访问, 如果上一个字母是向右+1, 那么下一个字母的遍历方向就不能是左, 依次向4个方向回溯即可。
class Solution {boolean res = false;// 上,下,左,右boolean flag[][];int leni = -1, lenj = -1;public boolean exist(char[][] board, String word) {leni = board.length;lenj = board[0].length;flag = new boolean[leni][lenj];for (int i = 0; i < leni; i++) {for (int j = 0; j < lenj; j++) {if (res == true) {return res;}if (board[i][j] == word.charAt(0)) {flag[i][j] = true;dfs(board, word, 1, i, j);flag[i][j] = false;}}}return res;}public void dfs(char[][] board, String word, int curLen, int curi, int curj) {if (res || curLen == word.length()) {res = true;return;}if (curi + 1 < leni && flag[curi + 1][curj] == false && word.charAt(curLen) == board[curi + 1][curj]) {flag[curi + 1][curj] = true;dfs(board, word, curLen + 1, curi + 1, curj);flag[curi + 1][curj] = false;}if (curi - 1 >= 0 && flag[curi - 1][curj] == false && word.charAt(curLen) == board[curi - 1][curj]) {flag[curi - 1][curj] = true;dfs(board, word, curLen + 1, curi - 1, curj);flag[curi - 1][curj] = false;}if (curj + 1 < lenj && flag[curi][curj + 1] == false && word.charAt(curLen) == board[curi][curj + 1]) {flag[curi][curj + 1] = true;dfs(board, word, curLen + 1, curi, curj + 1);flag[curi][curj + 1] = false;}if (curj - 1 >= 0 && flag[curi][curj - 1] == false && word.charAt(curLen) == board[curi][curj - 1]) {flag[curi][curj - 1] = true;dfs(board, word, curLen + 1, curi, curj - 1);flag[curi][curj - 1] = false;}}
}
82. 删除链表中的重复元素2
https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/description/
- 确保有一个哑结点,始终置身事外
- 做好判npe
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode deleteDuplicates(ListNode head) {if (head == null) {return null;}ListNode pre = new ListNode(-1000);pre.next = head;ListNode res = pre;while (head != null && head.next != null) {if (head.val == head.next.val) {while (head != null && head.next != null && head.val == head.next.val) {head = head.next;}pre.next = head.next;head = head.next;} else {pre = head;head = head.next;}}return res.next;}
}
83. 删除链表中的重复元素1
https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/description/
解题思路:
当前节点与下一个节点比对, 如果值相等,直接去掉中间节点, while条件是中间节点
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode deleteDuplicates(ListNode head) {if (head == null) {return head;}ListNode cur = head;while(cur.next != null) {if (cur.val == cur.next.val) {cur.next = cur.next.next;} else {cur = cur.next;}}return head;}
}
94. 二叉树的中序遍历——树——三刷
https://leetcode.cn/problems/binary-tree-inorder-traversal/?envType=study-plan-v2&envId=top-100-liked
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<Integer>();inorder(root, res);return res;}public void inorder(TreeNode root, List<Integer> res) {if (root == null) {return;}inorder(root.left, res);res.add(root.val);inorder(root.right, res);}
}
迭代写法:
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<Integer>();List<TreeNode> queue = new LinkedList<TreeNode>();while (root != null || !queue.isEmpty()) {while (root != null) {queue.addLast(root);root = root.left;}root = queue.getLast();queue.removeLast();res.add(root.val);root = root.right;}return res;}
}
三刷,直接秒的
class Solution {List<Integer> res = new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {// 左根右traversal(root);return res;}public void traversal(TreeNode root) {if (root == null) {return;}traversal(root.left);res.add(root.val);traversal(root.right);}
}
98. 验证二叉搜索树——树&递归——二刷
二叉搜索树的中序遍历,一定是递增的,基于此来校验
这里核心要理解层序遍历的底层原理,多理解
class Solution {public boolean isValidBST(TreeNode root) {List<TreeNode> list = new LinkedList<TreeNode>();long val = -Long.MAX_VALUE;while (!list.isEmpty() || root != null) {while (root != null) {list.addLast(root);root = root.left;}root = list.getLast();list.removeLast();if (root.val <= val) {return false;}val = root.val;root = root.right;}return true;}
}
二刷:递归实现中序遍历,进而判断,直接秒,我个人的习惯:前中后序用递归,层序用遍历
class Solution {boolean res = true;Long min = -Long.MAX_VALUE;public boolean isValidBST(TreeNode root) {if (root == null) {return true;}midCircle(root);return res;}public void midCircle(TreeNode root) {// 边界条件if (root == null) {return;}if (!res) {return;}// 中序-左节点midCircle(root.left);// 中序-根节点if (root.val <= min) {res = false;return;}min = Math.max(min, root.val);// 中序-右节点midCircle(root.right);}
}
101. 对称二叉树——递归&树
https://leetcode.cn/problems/symmetric-tree/?envType=study-plan-v2&envId=top-100-liked
思路:深搜, 缺节点false,不缺节点但是val不同false, 其他场景true
核心处理点在于参数是某个节点的左子树和右子树,root可以重复写入。
class Solution {public boolean isSymmetric(TreeNode root) {return check(root, root);}public boolean check(TreeNode p, TreeNode q) {if (p == null && q == null) {return true;}if (p == null || q == null) {return false;}return p.val == q.val && check(p.left, q.right) && check(p.right, q.left);}
}
二刷实现:
思考:分类讨论各种场景, 对我个人而言更直观简单清晰
class Solution {boolean res = true;public boolean isSymmetric(TreeNode root) {solution(root, root);return res;}public void solution(TreeNode leftNode, TreeNode rightNode) {if (res == false) {return;}if (leftNode == null && rightNode == null) {return;}if (leftNode == null && rightNode != null) {res = false;return;}if (leftNode != null && rightNode == null) {res = false;return;}if (leftNode != null && rightNode != null) {// 1. 值是否相等if (leftNode.val != rightNode.val) {res = false;return;}// 2. 左右子树是否对称solution(leftNode.left, rightNode.right);solution(leftNode.right, rightNode.left);}return;}
}
三刷:直接秒,递归做多了,顺其自然理出来逻辑,更丝滑了
class Solution {public boolean isSymmetric(TreeNode root) {if (root == null) {return true;}return symmetric(root, root);}public boolean symmetric(TreeNode n1, TreeNode n2) {if (n1 == null && n2 == null) {return true;}if (n1 == null || n2 == null) {return false;}if (n1.val != n2.val) {return false;}return symmetric(n1.left, n2.right) && symmetric(n1.right, n2.left);}
}
102. 二叉树的层序遍历——树的广搜*
https://leetcode.cn/problems/binary-tree-level-order-traversal/?envType=study-plan-v2&envId=top-100-liked
解题思路:直接广搜,注意二位数组的构造方式, 以及双向链表的出入等
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> res = new ArrayList<List<Integer>>();if (root == null) {return res;}List<TreeNode> list = new LinkedList<TreeNode>();list.addFirst(root);while (!list.isEmpty()) {int size = list.size();List<Integer> tempRes = new ArrayList<Integer>();while (size != 0) {TreeNode node = list.getFirst();list.removeFirst();size--;tempRes.add(node.val);if (node.left != null) {list.addLast(node.left);}if (node.right != null) {list.addLast(node.right);}}res.add(tempRes);}return res;}
}
104. 二叉树的最大深度——广搜——三刷
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
示例:输入:root = [3,9,20,null,null,15,7]
输出:3
示例 2:
解法思考:对于二叉树遍历,很自然的就想到深搜和广搜,但是对于实际生产中,递归并不常用,因为其性能开销较大,可能会导致栈溢出, 所以先尝试使用广搜来解题:
另外,这道题笔者认为用广搜更舒服
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public int maxDepth(TreeNode root) {// 判空if (root == null) {return 0;}List<TreeNode> queue = new LinkedList<TreeNode>();queue.addFirst(root);int res = 0;while (!queue.isEmpty()) {int size = queue.size();while (size > 0) {TreeNode node = queue.getFirst();queue.removeFirst();if (node.left != null) {queue.addLast(node.left);}if (node.right != null) {queue.addLast(node.right);}size--;}res++;}return res;}
}
同样附深搜解法吧
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public int maxDepth(TreeNode root) {// 判空if (root == null) {return 0;} else {int leftHeight = maxDepth(root.left);int rightHeight = maxDepth(root.right);return Math.max(leftHeight, rightHeight) + 1;}}
}
二刷:层序遍历直接秒
class Solution {public int maxDepth(TreeNode root) {// 最大深度,层序遍历秒了if (root == null) {return 0;}int res = 0;List<TreeNode> deque = new LinkedList<>();deque.addLast(root);while (!deque.isEmpty()) {// 每层的遍历res++;int size = deque.size();for (int i = 0; i < size; i++) {TreeNode node = deque.removeFirst();if (node.left != null) {deque.addLast(node.left);}if (node.right != null) {deque.addLast(node.right);}}}return res;}
}
105. 从前序与中序遍历序列构造二叉树——树&递归*
https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/submissions/569351084/?envType=study-plan-v2&envId=top-100-liked
解题思路:前序序列第一个节点是根节点, 去中序序列中找到对应位置, 位置左侧就是左子树,位置右侧就是右子树, 然后根据中序序列的左子树数量, 定位前序序列中左子树和右子树区间, 然后递归向下
边界场景是,l > r
注意对细节的处理
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {Map<Integer, Integer> indexMap = new HashMap<>();public TreeNode buildTree(int[] preorder, int[] inorder) {int n = inorder.length;for (int i = 0; i < n; i++) {indexMap.put(inorder[i], i);}TreeNode node = build(preorder, inorder, 0, n - 1, 0, n - 1);return node;}public TreeNode build(int[] preList, int[] inList, int pl, int pr, int il, int ir) {if (pl > pr) {return null;}// 1. 获取、构造根节点int rootPreIndex = pl;int rootInIndex = indexMap.get(preList[rootPreIndex]);TreeNode rootNode = new TreeNode(preList[rootPreIndex]);// 2. 计算左子树数据长度int lsize = rootInIndex - il;// 3. 构造左右子树rootNode.left = build(preList, inList, pl + 1, pl + lsize, il, rootInIndex - 1);rootNode.right = build(preList, inList, pl + lsize + 1, pr, rootInIndex + 1, ir);return rootNode;}
}
108. 将有序数组转换为二叉搜索树——树&递归&二分——三刷
https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/?envType=study-plan-v2&envId=top-100-liked
核心思路:无论数组内有多少个null节点,数组中间的数,一定是根节点, 同理,中间的数左边子数组,以及中间的数右边子数组,也遵循这个规律, 按照递归构建即可。
什么情况下某个节点为null呢? 当子数组内数据量为0时,进一步的, 当l > r
时。
class Solution {public TreeNode sortedArrayToBST(int[] nums) {// 本题核心逻辑,不管数组怎么变化,中间的数字一定是根节点, 用递归的思想考虑,前者同样适用于根节点左侧的子数组和右侧的子数组// 递归终止条件:l > r,说明区间内已经没有数据,自然返回0TreeNode node = buildSearchTree(nums, 0, nums.length - 1);return node;}public TreeNode buildSearchTree(int[] nums, int l, int r) {if (l > r) {return null;}int mid = (l + r) >> 1;TreeNode node = new TreeNode(nums[mid]);node.left = buildSearchTree(nums, l, mid - 1);node.right = buildSearchTree(nums, mid + 1, r);return node;}
}
二刷:
class Solution {public TreeNode sortedArrayToBST(int[] nums) {// 1. 中间节点一定是根节点// 2. 只有左小于右时有值,否则是nullint l = 0, r = nums.length - 1;TreeNode node = build(nums, l, r);return node;}public TreeNode build(int[] nums, int l, int r) {if (l > r) {return null;}int mid = (l + r) >> 1;TreeNode node = new TreeNode(nums[mid]);node.left = build(nums, l, mid - 1);node.right = build(nums, mid + 1, r);return node;}
}
三刷:比较丝滑,直接秒
class Solution {public TreeNode sortedArrayToBST(int[] nums) {// 二叉搜索树,左节点小于根节点,右节点大于根节点, 基于有序数组,二分构造二叉搜索树即可if (nums.length == 0) {return null;}return build(nums, 0, nums.length - 1);}public TreeNode build(int[] nums, int l, int r) {// 边界条件if (l > r) {return null;}if (l == r) {return new TreeNode(nums[l]);}// 递归构造搜索树int mid = (l + r) >> 1;TreeNode root = new TreeNode(nums[mid]);root.left = build(nums, l, mid - 1);root.right = build(nums, mid + 1, r);return root;}
}
114. 二叉树展开为链表——树&规律——二刷
https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/?envType=study-plan-v2&envId=top-100-liked
解题思路:我认为是规律推导题, 具体见官方题解第三种解法,动画非常清晰。
class Solution {public void flatten(TreeNode root) {TreeNode node = root;while (node != null) {if (node.left != null) {TreeNode n1 = node.left;TreeNode n2 = n1;while (n2.right != null) {n2 = n2.right;}n2.right = node.right;node.left = null;node.right = n1;}node = node.right;}}
}
二刷:看了思路,本质就是模拟,先序遍历下,右子树一定是等左子树全部遍历完,在遍历,所以就可以把右子树接到左子树的最右节点的右侧,再把左子树挪到右子树这里, 循环即可。
class Solution {public void flatten(TreeNode root) {build(root);}public void build(TreeNode root) {if (root == null) {return;}while (root != null) {if (root.left != null && root.right != null) {TreeNode left = root.left;TreeNode right = root.right;// 获取右子树,插入到左子树的最右侧TreeNode leftLastRoot = root.left;while (leftLastRoot.right != null) {leftLastRoot = leftLastRoot.right;}leftLastRoot.right = right;// 左子树挪到右子树的位置root.right = root.left;root.left = null;} else if (root.left != null) {root.right = root.left;root.left = null;}root = root.right;}}
}
118. 杨辉三角——DP——二刷
https://leetcode.cn/problems/pascals-triangle/?envType=study-plan-v2&envId=top-100-liked
看图找规律即可。
class Solution {public List<List<Integer>> generate(int numRows) {List<List<Integer>> res = new ArrayList<>();for (int i = 0; i < numRows; i++) {List<Integer> cur = new ArrayList<>();for (int j = 0; j <= i; j++) {if (i == 0 || j == 0 || j == i) {cur.add(1);} else {cur.add(res.get(i-1).get(j-1) + res.get(i-1).get(j));}}res.add(cur);}return res;}
}
二刷代码:写的稍微不流畅,这里需要注意边界处理
class Solution {public List<List<Integer>> generate(int numRows) {if (numRows == 0) {return null;}List<Integer> firstNums = new ArrayList<>();firstNums.add(1);List<List<Integer>> res = new ArrayList<>();res.add(new ArrayList<Integer>(firstNums));for (int i = 2; i <= numRows; i++) {List<Integer> newNums = new ArrayList<>();for (int j = 1; j <= i; j++) {if (j == 1 || j == i) {newNums.add(1);} else {newNums.add(res.get(i - 2).get(j - 1) + res.get(i - 2).get(j - 2));}}res.add(newNums);}return res;}
}
121. 买卖股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/?envType=study-plan-v2&envId=top-100-liked
解题思路:贪心(贪心本质是当前最优策略, 每次遍历,都考虑新一次的最优策略,是否优于当前最优策略,如果是,则替换最优策略), 遍历一遍, 记录到每个元素时,最小的值, 然后用当前元素减去最小值, 获取过程中最大差值返回即可。
PS:有点像最小栈的思路
class Solution {public int maxProfit(int[] prices) {int minPrice = Integer.MAX_VALUE;int maxValue = 0;for (int i = 0; i < prices.length; i++) {if (prices[i] < minPrice) {minPrice = prices[i];} else {maxValue = Math.max(prices[i] - minPrice, maxValue);}}return maxValue;}
}
124. 二叉树中的最大路径和——树&递归——一刷
https://leetcode.cn/problems/binary-tree-maximum-path-sum/?envType=study-plan-v2&envId=top-100-liked
自己做出80%case,但还是看了题解,需要二刷
class Solution {int res = -Integer.MAX_VALUE;public int maxPathSum(TreeNode root) {if (root == null) {return 0;}getMaxNum(root);return res;}public int getMaxNum(TreeNode root) {if (root == null) {return 0;}int leftNum = getMaxNum(root.left);int rightNum = getMaxNum(root.right);// 左右根节点的负数处理int finLeftNum = leftNum > 0 ? leftNum : 0;int finRightNum = rightNum > 0 ? rightNum : 0;res = Math.max(res, finLeftNum + finRightNum + root.val);return Math.max(finLeftNum, finRightNum) + root.val;}
}
128. 最长连续序列——技巧*
https://leetcode.cn/problems/longest-consecutive-sequence/submissions/570503268/?envType=study-plan-v2&envId=top-100-liked
遍历每个元素, 如果这个元素-1不存在,那么说明这个元素是起始元素,然后向后逐个遍历,并更新最大长度, 这个-1很巧妙
class Solution {public int longestConsecutive(int[] nums) {Set<Integer> set = new HashSet<Integer>();for (int i = 0; i < nums.length; i++) {set.add(nums[i]);}int maxLen = 0;for (Integer num : set) {int curLen = 0;if (!set.contains(num - 1)) {int j = num;while (set.contains(j)) {j++;curLen++;}maxLen = Math.max(maxLen, curLen);}}return maxLen;}
}
二刷:
时间复杂度为什么是On?
- 如果num-1不存在,那么说明是序列头,遍历,这里看似最差时间复杂度是On,并且在循环内, 但是遍历过的元素,都属于第二种情况,也就是不需要重复遍历的
- 如果num-1存在,那么说明不是序列头,不遍历,O1
因此整体时间复杂度是On
class Solution {public int longestConsecutive(int[] nums) {if (nums.length == 0) {return 0;}Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {map.put(nums[i], 1);}int res = 1;for (Integer num : map.keySet()) {if (!map.containsKey(num - 1)) {int t = 0;int tmpNum = num;while (map.containsKey(tmpNum)) {tmpNum++;t++;}res = Math.max(res, t);}}return res;}
}
131. 分割回文串——回溯——二刷
解题思路:经典回溯思路, dfs中, 对每一个位置+1,+2,…,+n进行全排列的遍历,然后回溯下一个位置也按照此思路,直到遍历完, 其中用回文方法判断是否符合即可。
https://leetcode.cn/problems/palindrome-partitioning/?envType=study-plan-v2&envId=top-100-liked
class Solution {List<List<String>> res = new ArrayList<>();List<String> cur = new ArrayList<>();public List<List<String>> partition(String s) {dfs(s, 0);return res;}public void dfs(String s, int curLen) {if (s.length() == curLen) {res.add(new ArrayList(cur));return;}for (int i = curLen + 1; i <= s.length(); i++) {String t = s.substring(curLen, i);if (isRequest(t)) {cur.add(t);dfs(s, i);cur.remove(cur.size() - 1);}}}public boolean isRequest(String s) {int i = 0, j = s.length() - 1;while (i < j) {if (s.charAt(i) != s.charAt(j)) {return false;}i++; j--;}return true;}
}
类似全排列的思路,换成字符串就行
class Solution {List<List<String>> res = new ArrayList<>();public List<List<String>> partition(String s) {// 思路:类似全排列, 每次都从curLen开始,从1到len逐个分割,如果到len了,就add进去List<String> curList = new ArrayList<>();dfs(curList, 0, s);return res;}public void dfs(List<String> curList, int curLen, String s) {int len = s.length();if (curLen == len) {res.add(new ArrayList<>(curList));return;}for (int i = curLen + 1; i <= len; i++) {String curS = s.substring(curLen, i);if (isReserve(curS)) {curList.add(curS);dfs(curList, i, s);curList.remove(curList.size() - 1);}}}public boolean isReserve(String s) {char[] cc = s.toCharArray();int l = 0, r = cc.length - 1;while (l < r) {if (cc[l] != cc[r]) {return false;}l++; r--;}return true;}
}
136. 只出现一次的数字——技巧
https://leetcode.cn/problems/single-number/submissions/571042669/?envType=study-plan-v2&envId=top-100-liked
解题思路:异或。
class Solution {public int singleNumber(int[] nums) {if (nums.length == 1) {return nums[0];}for (int i = 1; i < nums.length; i++) {nums[i] ^= nums[i - 1];}return nums[nums.length - 1];}
}
139. 单词拆分——DP*
https://leetcode.cn/problems/word-break/?envType=study-plan-v2&envId=top-100-liked
解题思路:如下,有点类似背包,运用还不太熟练
class Solution {public boolean wordBreak(String s, List<String> wordDict) {// 解题思路:设目标字符串为abcde// 字符串是否符合,取决于i+1-j是单词, 并且0-i是若干单词// 怎么判断0-i是单词?排列组合,符合任意条件,则说明是Map<String, Integer> map = new HashMap<>();for (int i = 0; i < wordDict.size(); i++) {map.put(wordDict.get(i), 1);}int len = s.length();boolean[] dp = new boolean[len + 1];dp[0] = true;for (int i = 1; i <= len; i++) {for (int j = 0; j < i; j++) {if (dp[j] == true && map.containsKey(s.substring(j, i))) {dp[i] = true;break;}}}return dp[len];}
}
141. 环形链表——链表-二刷
https://leetcode.cn/problems/linked-list-cycle/
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
第一思路,显然是使用Hash来判断节点是否被重复访问过,
时间复杂度On,空间复杂度On
让我们看一个「龟兔赛跑」的故事
如果我们设置一个fast指针,一个slow指针,fast指针每次移动2步,slow指针每次移动1步,如果链表内部有环,那么一定存在某次循环中,fast指针域slow指针指向同一个内存地址,这就是弗洛伊德算法(Floyd),也叫快慢指针。
让我们思考一下循环停止需要的条件:
- 存在环的场景:快慢指针相遇
- 不存在环的场景:遍历到null(即结尾)
于是代码如下:
/*** Definition for singly-linked list.* class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/
public class Solution {public boolean hasCycle(ListNode head) {// 入参校验if (head == null || head.next == null) {return false;}// 定义快慢指针ListNode slowNode = head;ListNode fastNode = head.next;// 终止条件1:快慢指针相遇,返回truewhile(slowNode != fastNode) {// 终止条件2:快指针null,代表遍历到头,返回falseif (fastNode == null || fastNode.next == null) {return false;}slowNode = slowNode.next;fastNode = fastNode.next.next;}return true;}
}
快慢指针解法的实现,注意以下几个细节:
5. 循环终止条件只有两个(要么快慢指针相遇,要么快指针跑到头),即最少有2个return,多了的都可以优化
6. 入参的空值校验
7. 合理的注释
8. 对于变量的合理命名(如slowNode是比slow更好的)
二刷:
public class Solution {public boolean hasCycle(ListNode head) {if (head == null || head.next == null) {return false;}// 定义快慢指针,快指针每次走两步,慢指针每次走一步,如果存在环,必然相交ListNode fast = head, slow = head;while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if (fast != null && fast == slow) {return true;}}return false;}
}
146. LRU
https://leetcode.cn/problems/lru-cache/?envType=study-plan-v2&envId=top-100-liked
1 5 1 4 2
1(6)-一个DLinkedNode结构
5-5个初始变量
1(6)-一个初始化
4-4个基本方法
2-get和put
class LRUCache {public class DLinkedNode {// 这里一定要有key,否则不知道move哪个private int key;private int value;private DLinkedNode prev;private DLinkedNode next;private DLinkedNode() {}private DLinkedNode(int key, int value) {this.key = key;this.value = value;}}public int size = 0;public int capacity;public Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();public DLinkedNode head;public DLinkedNode tail;public LRUCache(int capacity) {this.size = 0;this.capacity = capacity;head = new DLinkedNode();tail = new DLinkedNode();head.next = tail;tail.prev = head;}// addToHead moveToHead removeNode removeTailpublic void addToHead(DLinkedNode node) {node.prev = head;node.next = head.next;// 这里顺序一定一定要注意, 一定是先next.prev,否则next指向变了,next.prev指向也变了head.next.prev = node;head.next = node;}public void removeNode(DLinkedNode node) {node.prev.next = node.next;node.next.prev = node.prev;}public void moveToHead(DLinkedNode node) {removeNode(node);addToHead(node);}public DLinkedNode removeTail() {DLinkedNode res = tail.prev;removeNode(res);return res;}public int get(int key) {// 获取不到,返回-1, 获取到,返回+调整链表结构DLinkedNode node = cache.get(key);if (node == null) {return -1;} else {moveToHead(node);return node.value;}}public void put(int key, int value) {// 存在,直接替换; 不存在,插入链表头,插入map, 同时判断容量,超过容量剔除队尾DLinkedNode node = cache.get(key);if (node == null) {DLinkedNode newNode = new DLinkedNode(key, value);addToHead(newNode);cache.put(key, newNode);size++;if (size > capacity) {DLinkedNode tail = removeTail();cache.remove(tail.key);size--;}} else {node.value = value;moveToHead(node);}}
}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/
三刷代码:调试了一下, 然后忘记put时也要moveToHead了
class LRUCache {// 1 5 1 4 2// map作用是取数据O1,链表作用是,判断最久未使用public class DLinkNode {int key;int value;DLinkNode next;DLinkNode pre;DLinkNode() {}DLinkNode(int key, int value) {this.key = key;this.value = value;}}int size;int capacity;DLinkNode head;DLinkNode tail;Map<Integer, DLinkNode> map;public LRUCache(int capacity) {this.size = 0;this.capacity = capacity;this.head = new DLinkNode();this.tail = new DLinkNode();head.next = tail;tail.pre = head;map = new HashMap<>();}// moveToHead, removeNode, removeTail, addToHeadpublic void addToHead(DLinkNode node) {head.next.pre = node;node.next = head.next;head.next = node;node.pre = head;}public void moveToHead(DLinkNode node) {removeNode(node);addToHead(node);}public void removeNode(DLinkNode node) {node.pre.next = node.next;node.next.pre = node.pre;}public DLinkNode removeTail() {DLinkNode node = tail.pre;removeNode(node);return node;}public int get(int key) {// 如果有,返回,并moveToHeadif (map.containsKey(key)) {DLinkNode node = map.get(key);moveToHead(node);return node.value;} else {return -1;}}public void put(int key, int value) {if (map.containsKey(key)) {// 如果包含,直接替换value,同时moveToHeadDLinkNode node = map.get(key);node.value = value;moveToHead(node);} else {DLinkNode newNode = new DLinkNode(key, value);if (size == capacity) {// 链表中逐出旧的,添加新的; map中移除旧的,再添加新的DLinkNode oldNode = removeTail();addToHead(newNode);map.remove(oldNode.key);map.put(newNode.key, newNode);} else {// 链表和map增加对应的值即可size += 1;addToHead(newNode);map.put(key, newNode);}}}
}/*** Your LRUCache object will be instantiated and called as such:* LRUCache obj = new LRUCache(capacity);* int param_1 = obj.get(key);* obj.put(key,value);*/
153. 寻找旋转排序数组中的最小值——二分*
https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/?envType=study-plan-v2&envId=top-100-liked
解题思路:二分数组,最小值,一定在非递增的那半边(因为最小值是转折点的起始),按照这个思路找即可
需要注意对边界条件的判断,我这里取了巧
class Solution {public int findMin(int[] nums) {int res = 6000;// 解题思路:二分数组,最小值,一定在非递增的那半边(因为最小值是转折点的起始),按照这个思路找即可int l = 0, r = nums.length - 1;while (l <= r) {int mid = (l + r) >> 1;res = Math.min(res, nums[l]);res = Math.min(res, nums[r]);res = Math.min(res, nums[mid]);if (nums[l] <= nums[mid]) {l = mid + 1;} else {r = mid - 1;}}return res;}
}
155. 最小栈——栈——二刷
https://leetcode.cn/problems/min-stack/description/?envType=study-plan-v2&envId=top-100-liked
核心思路:维护一个最小栈,每次入栈,都判断最小栈栈顶元素是否小于当前值,如果小于,则把当前元素压入, 如果大于,则重复压入当前栈顶元素
class MinStack {List<Integer> stack;List<Integer> minStack;public MinStack() {stack = new LinkedList<Integer>();minStack = new LinkedList<Integer>();minStack.addFirst(Integer.MAX_VALUE);}public void push(int val) {stack.addFirst(val);if (minStack.get(0) > val) {minStack.addFirst(val);} else {// 这句话非常重要minStack.addFirst(minStack.get(0));}}public void pop() {stack.removeFirst();minStack.removeFirst();}public int top() {return stack.get(0);}public int getMin() {return minStack.get(0);}
}
class MinStack {List<Integer> stack;List<Integer> minStack;public MinStack() {stack = new LinkedList<>();minStack = new LinkedList<>();Integer minNum = Integer.MAX_VALUE;minStack.addFirst(minNum);}public void push(int val) {stack.addFirst(val);if (val < minStack.getFirst()) {minStack.addFirst(val);} else {minStack.addFirst(minStack.getFirst());}}public void pop() {stack.removeFirst();minStack.removeFirst();}public int top() {return stack.getFirst();}public int getMin() {return minStack.getFirst();}
}/*** Your MinStack object will be instantiated and called as such:* MinStack obj = new MinStack();* obj.push(val);* obj.pop();* int param_3 = obj.top();* int param_4 = obj.getMin();*/
160. 相交链表——链表
https://leetcode.cn/problems/intersection-of-two-linked-lists/?envType=study-plan-v2&envId=top-100-liked
A+B = B+A
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB == null) {return null;}ListNode firstHeadA = headA;ListNode firstHeadB = headB;boolean headAChange = false, headBChange = false;while (headA != null && headB != null) {if (headA == headB) {return headA;}headA = headA.next;headB = headB.next;if (headA == null && !headAChange) {headA = firstHeadB;headAChange = true;}if (headB == null) {headB = firstHeadA;headBChange = true;}}return null;}
}
二刷:直接秒了
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) {* val = x;* next = null;* }* }*/
public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {// 解题思路:A+B = B+A, A和B一起遍历,如果A到头就从B处开始,如果B到头就从A处开始,直到两个节点相等// 记录首节点ListNode headA1 = headA, headB1 = headB;// 记录遍历到末尾的次数int countTail = 0;while (countTail <= 2) {if (headA == headB) {return headA;}headA = headA.next;if (headA == null) {headA = headB1;countTail++;}headB = headB.next;if (headB == null) {headB = headA1;countTail++;}}return null;}
}
169. 多数元素——技巧*
https://leetcode.cn/problems/majority-element/solutions/146074/duo-shu-yuan-su-by-leetcode-solution/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码
class Solution {public int majorityElement(int[] nums) {// 思考一个类似打擂台的思路,如果两个数相同, 则count+1,如果不同,则-1,如果count为0,则换另一个数,如果某一个数大于半数, 那么最后剩下的值一定是这个数Integer res = null;int count = 0;for (int i = 0; i < nums.length; i++) {if (count == 0) {res = nums[i];}if (res == nums[i]) {count += 1;} else {count -= 1;}}return res;}
}
189. 轮转数组——数组——二刷
最简单的思路:声明一个新数组来存储,然后赋值即可。
第二种思路,数组翻转, 由于移动k位,则代表最后k个数字一定出现在数组的前k个位置,因此先整体翻转数组,这样最后k个数字就到了前k个位置,然后对这k个数字翻转, 这样顺序转正, 再然后对k-n个数字翻转,完成。
下面给出两种思路的代码:
class Solution {public void rotate(int[] nums, int k) {int len = nums.length;k = k % len;if (k == 0) {return;}int[] res = new int[len]; for (int i = 0; i < len; i++) {int move = (i + k) % len;res[move] = nums[i];}for (int i = 0; i < len; i++) {nums[i] = res[i];}}
}
class Solution {public void rotate(int[] nums, int k) {int len = nums.length;k = k % len;reserve(nums, 0, len - 1);reserve(nums, 0, k - 1);reserve(nums, k, len - 1);}public void reserve(int[] nums, int l, int r) {for (; l < r; l++, r--) {int t = nums[l];nums[l] = nums[r];nums[r] = t;}}
}
二刷:更圆融如意,直接秒
class Solution {public void rotate(int[] nums, int k) {// 1. k取余, 三次翻转int len = nums.length;k = k % len;reserve(nums, 0, len);reserve(nums, 0, k);reserve(nums, k, len);}public void reserve(int[] nums, int i, int j) {j -= 1;while (i < j) {swap(nums, i, j);i++; j--;}}public void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}
198. 打家劫舍——DP——二刷
https://leetcode.cn/problems/house-robber/solutions/263856/da-jia-jie-she-by-leetcode-solution/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码,推出边界条件+状态转移方程
另外,还有更优化的解法是用两个变量代替数组
class Solution {public int rob(int[] nums) {// 如果只有一间房屋,偷窃最大额是房屋1金额// 如果有两间房屋,偷窃最大额是max(房屋1金额, 房屋2金额)// 如果有k间房屋,那么有两种情况// 情况1,偷第k间,最大偷窃金额 = 第k间金额 + 前k-2间最大金额// 情况2,不偷第k间,最大偷窃金额 = 前k-1间最大金额int len = nums.length;int[] maxMoneyList = new int[len];for (int i = 0; i < len; i++) {if (i == 0) {// 边界场景1maxMoneyList[i] = nums[i]; } else if (i == 1) {// 边界场景2maxMoneyList[i] = Math.max(nums[i-1], nums[i]);} else {// 状态转移公式maxMoneyList[i] = Math.max(nums[i] + maxMoneyList[i - 2], maxMoneyList[i - 1]);}}return maxMoneyList[len - 1];}
}
二刷:DP裸题,丝滑秒杀
class Solution {public int rob(int[] nums) {// DP裸题// 边界条件: 只有一间房时,最高金额为该房屋金额; 有两间房屋时,最高金额为两间中更高的一间// DP公式:有n间房屋时, // 场景1:偷第n间房屋,最高金额为第n间房屋金额 + 前n-2个房屋总最高金额// 场景2:不偷第n间房屋,最高金额为 前n-1个房屋总最高金额// 因此DP公式:fn = max(n+fn-2, fn-1);int len = nums.length;if (len == 0) {return 0;} else if (len == 1) {return nums[0];} else if (len == 2) {return Math.max(nums[0], nums[1]);}int dp[] = new int[len + 1];dp[1] = nums[0];dp[2] = Math.max(nums[0], nums[1]);for (int i = 2; i < len; i++) {dp[i + 1] = Math.max(nums[i] + dp[i - 1], dp[i]);}return dp[len];}
}
199. 二叉树的右视图
https://leetcode.cn/problems/binary-tree-right-side-view/?envType=study-plan-v2&envId=top-100-liked
解题思路:层序遍历,每层取最后一个节点即可。
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> res = new ArrayList<>();if (root == null) {return res;}List<TreeNode> list = new LinkedList<>();list.addLast(root);while (!list.isEmpty()) {int size = list.size();TreeNode last = list.getLast();res.add(last.val);while (size-- > 0) {TreeNode node = list.removeFirst();if (node.left != null) {list.addLast(node.left);}if (node.right != null) {list.addLast(node.right);}}}return res;}
}
206. 反转链表——链表-三刷
https://leetcode.cn/problems/reverse-linked-list/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode reverseList(ListNode head) {// 解题思路:定义哑结点,三个指针互相走,第三个节点是引路的作用,同时定义哑结点,作用是把首节点当做普通节点一样处理, 最后哑结点置null即可。if (head == null || head.next == null) {return head;}ListNode n1 = head, n2 = head.next, n3 = head.next.next;n1.next = head;ListNode head1 = head;while (n2 != null) {n2.next = n1;if (n3 == null) {break;}n1 = n2;n2 = n3;n3 = n3.next;}head1.next = null;return n2;}
}
三刷:优化了代码,整体更丝滑
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public ListNode reverseList(ListNode head) {// 解题思路:定义哑结点,三个指针互相走,第三个节点是引路的作用,同时定义哑结点,作用是把首节点当做普通节点一样处理, 最后哑结点置null即可。if (head == null || head.next == null) {return head;}ListNode n1 = null, n2 = head, n3 = head.next;while (n2 != null) {n2.next = n1;if (n3 == null) {break;}n1 = n2;n2 = n3;n3 = n3.next;}return n2;}
}
215. 数组中第K个最大的元素——堆*
https://leetcode.cn/problems/kth-largest-element-in-an-array/description/?envType=study-plan-v2&envId=top-100-liked
核心思路:理解堆的构建过程,调整过程
class Solution {public int findKthLargest(int[] nums, int k) {int len = nums.length;// 建堆build(nums, len);// 找到第k个最大的元素,每移除一个元素,要重新调整堆for (int i = 1; i < k; i++) {swap(nums, 0, len-1);len--;adjust(nums, 0, len);}return nums[0];}public void build(int[] nums, int len) {// 遍历所有节点,从后往前调整,确保逐步有序for (int i = (len >> 1); i >= 0; i--) {adjust(nums, i, len);}}public void adjust(int[] nums, int cur, int len) {// 每次调整,保证了三个节点范围内根节点是最大的, 然后再逐步往下调整int i = cur;int l = cur * 2 + 1, r = cur * 2 + 2;if (l < len && nums[l] > nums[cur]) {cur = l;}if (r < len && nums[r] > nums[cur]) {cur = r;}if (i != cur) {swap(nums, i, cur);adjust(nums, cur, len);}} public void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}
二刷:
class Solution {public int findKthLargest(int[] nums, int k) {// 1. 建堆,从大到小依次遍历// 2. 调整堆,根节点是最大的, 如果不是,则调整, 然后把调整的哪个节点继续调整(因为扰动了该节点下的所有左右子树)int len = nums.length;buildHeap(nums, len);for (int i = 1; i < k; i++) {swap(nums, 0, len - 1);len--;modifyHeap(nums, 0, len);}return nums[0];}public void buildHeap(int[] nums, int len) {for (int i = (len >> 1); i >= 0; i--) {modifyHeap(nums, i, len);}}public void modifyHeap(int[] nums, int cur, int len) {if (cur >= len) {return;}int oCur = cur;int l = cur * 2 + 1;int r = cur * 2 + 2;if (l < len && nums[cur] < nums[l]) {cur = l;}if (r < len && nums[cur] < nums[r]) {cur = r;}if (oCur != cur) {swap(nums, oCur, cur);modifyHeap(nums, cur, len);}}public void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}
226. 翻转二叉树——树&递归——三刷
https://leetcode.cn/problems/invert-binary-tree/?envType=study-plan-v2&envId=top-100-liked
解题思路:递归,没什么好说的
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) {return null;}TreeNode left = invertTree(root.left);TreeNode right = invertTree(root.right);root.left = right;root.right = left;return root;}
}
二刷:更符合我个人的思考习惯,代码如下:
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) {return root;}TreeNode t = root.left;root.left = root.right;root.right = t;invertTree(root.left);invertTree(root.right);return root;}
}
三刷:二刷应该还看了题解的,三刷纯手推,更丝滑
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) {return null;}invert(root);return root;}public void invert(TreeNode node) {if (node == null) {return;}TreeNode t = node.left;node.left = node.right;node.right = t;invert(node.left);invert(node.right);}
}
230. 二叉搜索树中第k小的元素
https://leetcode.cn/problems/kth-smallest-element-in-a-bst/?envType=study-plan-v2&envId=top-100-liked
相关hot100题型:
- 二叉树的中序遍历
- 验证二叉搜索树
核心思路:二叉搜索树的中序遍历,是递增的,以此找第k小的元素
这里最好要学会中序遍历的迭代写法
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public int kthSmallest(TreeNode root, int k) {List<TreeNode> list = new LinkedList<TreeNode>();int num = 1;// 遍历所有节点while (root != null || !list.isEmpty()) {// 左遍历while (root != null) {list.addFirst(root);root = root.left;}// 获取最近塞入的一个节点root = list.getFirst();list.removeFirst();// 判断操作if (num == k) {return root.val;}num++;// 这个节点的左子树已经遍历完, 转而遍历这个节点的右子树root = root.right;}return -1;}
}
234. 回文链表——链表*——三刷
https://leetcode.cn/problems/palindrome-linked-list/?envType=study-plan-v2&envId=top-100-liked
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public boolean isPalindrome(ListNode head) {if (head == null || head.next == null) {return true;}// 1. 求长度int len = 0;ListNode head1 = head, head2 = head;while (head != null) {head = head.next;len++;}// 2. 获取节点长度是奇数还是偶数boolean isOu = false;if ((len % 2) == 0) {isOu = true;}// 3. 链表前半段反转// 前半段反转int time = 0;ListNode n1 = new ListNode(-1), n2 = head1, n3 = head1.next;n1.next = n2;// 遍历len/2次,包含哑结点的反转while (time < len / 2) {n2.next = n1;if (n3 == null) {break;}n1 = n2;n2 = n3;n3 = n3.next;time++;}// 去掉哑结点head1.next = null;// 4. 判断是否回文ListNode two = (isOu ? n2 : n3);ListNode one = n1;// 两个链表长度一定相等,所以不用复杂判定while (one != null) {if (one.val != two.val) {return false;}one = one.next;two = two.next;}return true;}
}
二刷:整体代码更清爽,更顺畅
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public boolean isPalindrome(ListNode head) {if (head == null || head.next == null) {return true;}// 1. 求长度,判断奇数还是偶数int len = 0;ListNode h1 = head;while (h1 != null) {len++;h1 = h1.next;}boolean isOu = (len % 2 == 0 ? true : false);// 2. 反转链表的前半部分,定义哑结点,头结点标准化ListNode n1 = new ListNode(-1);n1.next = head;ListNode n2 = head, n3 = head.next;int t = 0;while (t < len / 2) {if (t == 0) {n1.next = null;}n2.next = n1;n1 = n2;n2 = n3;n3 = n3.next;t++;}// 3. 前半部分和后半部分对比ListNode one = n1;ListNode two = isOu ? n2 : n3;while (one != null && two != null) {if (one.val != two.val) {return false;}one = one.next;two = two.next;}return true;}
}
236. 二叉树的最近公共祖先——树&递归——二刷
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/submissions/569498073/?envType=study-plan-v2&envId=top-100-liked
解题思路:
- DFS:使用DFS可以确保找到的第一个公共祖先是最近公共祖先,因为DFS是自底向上
- 节点满足条件:左子树包含目标节点、右子树包含目标节点、本身是目标节点, 三者满足其二,就说明是最近公共祖先,记录并返回即可
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/
class Solution {TreeNode res;public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 找到共同祖先的唯一场景:左子树包含、右子树包含、自身是一个节点, 三个条件满足其二// 为什么DFS遍历出来的第一个一定是最近公共祖先? 因为DFS是从叶子节点开始遍历的dfs(root, p, q);return res;}public int dfs(TreeNode root, TreeNode p, TreeNode q) {if (root == null) {return 0;}int a = (root.val == p.val || root.val == q.val) ? 1 : 0;int b = dfs(root.left, p, q);int c = dfs(root.right, p, q);if (a + b + c == 2) {res = root;}return (a + b + c) == 0 ? 0 : 1;}
}
二刷,还是看题解了,这个还需要三刷,核心是对最近公共祖先的理解不到位,最近公共祖先的要求,只能是左孩子+右孩子是, 或自身是+(左孩子是or右孩子是)
class Solution {TreeNode res = null;public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 最近公共祖先条件:左孩子+右孩子是, 或自身是+(左孩子是or右孩子是)dfs(root, p, q);return res;}public boolean dfs(TreeNode root, TreeNode p, TreeNode q) {if (root == null) {return false;}boolean selfHave = (root == p || root == q);boolean leftHave = dfs(root.left, p, q);boolean rightHave = dfs(root.right, p, q);// res赋值条件if ((leftHave && rightHave) || ((leftHave || rightHave) && selfHave)) {res = root;return true;}// 返回值:该root是否为truereturn selfHave || leftHave || rightHave;}
}
238. 除自身以外数组的乘积——数组——二刷
https://leetcode.cn/problems/product-of-array-except-self/?envType=study-plan-v2&envId=top-100-liked
解题思路:定义双指针,新数组初始值为1,左指针负责某个位置的值*
左边乘积; 右指针负责某个位置的值*
右边乘积
点评:巧妙, 通过初值为1,解决了不能乘以本值的问题, 通过左右指针,将乘积拆分为左半边和右半边两个连续的部分,然后遍历
class Solution {public int[] productExceptSelf(int[] nums) {// 核心思路:声明一个数组,初始值为1, 然后双指针,左指针的作用是这个位置左遍的乘积,右指针的作用是这个位置右边的乘积int len = nums.length;int[] res = new int[len];for (int i = 0; i < len; i++) {res[i] = 1;}int ls = 1, rs = 1;for (int l = 0, r = len - 1; l <= len - 1 && r >= 0; l++, r--) {res[l] *= ls;res[r] *= rs;ls *= nums[l];rs *= nums[r];}return res;}
}
二刷,想了一会直接秒,思路还是挺巧妙
class Solution {public int[] productExceptSelf(int[] nums) {int len = nums.length;// ans初始化为1int[] res = new int[len];for (int i = 0; i < len; i++) {res[i] = 1;}// 第一遍遍历,先乘左边的int lnum = 1, rnum = 1;for (int i = 0; i < len; i++) {res[i] = res[i] * lnum;lnum *= nums[i];}// 第二遍遍历,乘右边的for (int i = len - 1; i >= 0; i--) {res[i] *= rnum;rnum *= nums[i];}return res;}
}
240. 搜索二维矩阵二——矩阵&技巧——一刷
https://leetcode.cn/problems/search-a-2d-matrix-ii/solutions/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码
class Solution {public boolean searchMatrix(int[][] matrix, int target) {// 第一种解法二分,每次可以排除半行或者半列,时间复杂度Onlogn// 第二种解法,设想初始查找位置在右上角,该元素左侧所有树都小于他,该元素右侧所有树都大于他,所以每次可以排除整行或整列的数,时间复杂度Om+n// 其实还有第三种解法,前两种解法综合,时间复杂度Ologm+Olognint lenx = matrix.length, leny = matrix[0].length;int x = 0, y = leny - 1;while (x < lenx && y >= 0) {if (matrix[x][y] == target) {return true;}if (matrix[x][y] > target) {y--;} else {x++;}}return false;}
}
279. 完全平方数——DP*
https://leetcode.cn/problems/perfect-squares/?envType=study-plan-v2&envId=top-100-liked
解题思路:见代码,就是一个完全背包
class Solution {public int numSquares(int n) {// 解题思路:假设n为12,最小次数的计算只有三种结果:// 因为 11 + 1*1 = 12, 因此结果 = 达到11的最小次数 + 1// 因为 8 + 2*2 = 12, 因此结果 = 达到8的最小次数 + 1// 因为 3 + 3*3 = 12, 因此结果 = 达到3的最小次数 + 1// 对11、8、3的最小次数的求法,重复上述计算过程int[] t = new int[n + 1];t[0] = 0;for (int i = 1; i <= n; i++) {int min = Integer.MAX_VALUE;for (int j = 1; j * j <= i; j++) {min = Math.min(min, t[i-j*j] + 1);}t[i] = min;}return t[n];}
}
283. 移动零——双指针
https://leetcode.cn/problems/move-zeroes/?envType=study-plan-v2&envId=top-100-liked
一刷代码:
class Solution {public void moveZeroes(int[] nums) {int len = nums.length;// 双指针, 一个指针标记为0的, 一个指针标记不为0的,指针二永远在指针一后面int l1 = 0, l2 = 0;while (l1 < len && l2 < len) {while (l1 < len && nums[l1] != 0) {l1++;}l2 = l1 + 1;while (l2 < len && nums[l2] == 0) {l2++;}if (l2 < len) {swap(nums, l1, l2);}}}public void swap(int[] nums, int l1, int l2) {int t = nums[l1];nums[l1] = nums[l2];nums[l2] = t;}
}
二刷代码:
有一个非常牛逼的思路,即i不动,遍历j,如果j不等于0,就和i换位置,i++,这样保证所有非0的数字,都按顺序出现在前面,后面的自然就是0了
class Solution {public void moveZeroes(int[] nums) {// 遍历,如果发现,基于0的位置,向后遍历,遇到第一个非0的数据,交换int len = nums.length;int i = 0, j = 0;while (j < len) {if (nums[j] != 0) {swap(nums, i, j);i++;}j++;}}public void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}// class Solution {
// public void moveZeroes(int[] nums) {
// // 遍历,如果发现,基于0的位置,向后遍历,遇到第一个非0的数据,交换
// int len = nums.length;
// for (int i = 0; i < len; i++) {
// if (nums[i] == 0) {
// for (int j = i + 1; j < len; j++) {
// if (nums[j] != 0) {
// swap(nums, i, j);
// break;
// }
// }
// }
// }
// }// public void swap(int[] nums, int i, int j) {
// int t = nums[i];
// nums[i] = nums[j];
// nums[j] = t;
// }
// }
287. 寻找重复数——二分*-2
https://leetcode.cn/probl
ems/find-the-duplicate-number/?envType=study-plan-v2&envId=top-100-liked
class Solution {public int findDuplicate(int[] nums) {// 根据第一个用例倒推, 遍历数组,如果 <= n/2的值的数量, > n/2, 那么说明重复数据值在0-n/2之间,然后通过二分逐渐缩小范围int n = nums.length;int l = 1, r = n - 1, ans = -1;while (l <= r) {int mid = (l + r) / 2;int cnt = 0;for (int i = 0; i < n; i++) {if (nums[i] <= mid) {cnt ++;}}if (cnt > mid) {r = mid - 1;ans = mid;} else {l = mid + 1;}}return ans;}
}
二刷,依旧没做出来,卡在ans的赋值上, 很巧妙, 更熟练了
class Solution {public int findDuplicate(int[] nums) {// 二分,每次二分,都遍历int l = 1, r = nums.length - 1; int ans = -1;while (l <= r) {int mid = (l + r) >> 1;int count = 0;for (int i = 0; i <= nums.length - 1; i++) {if (nums[i] <= mid) {count++;}}if (count > mid) {r = mid - 1;// 这个ans巧妙, 因为如果不-1,会死循环, 如果-1,下次循环的mid可能不是正确结果,所以先记录下来ans = mid;} else {l = mid + 1;}}return ans;}
}
300. 最长递增子序列——DP*
https://leetcode.cn/problems/longest-increasing-subsequence/?envType=study-plan-v2&envId=top-100-liked
class Solution {public int lengthOfLIS(int[] nums) {// 解题思路:声明记忆数组DP,记录每个位置上最长子序列,然后新遍历的位置,依次和前面每一个元素比较,如果大于这个元素,就在这个元素最长子序列的基础上+1// 边界条件:dp[0] = 1int len = nums.length;int dp[] = new int[len];dp[0] = 1;int res = 1;for (int i = 1; i < len; i++) {dp[i] = 1;for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}res = Math.max(dp[i], res);}return res;}
}
322. 零钱兑换——DP*
https://leetcode.cn/problems/coin-change/?envType=study-plan-v2&envId=top-100-liked
经典完全背包,注意考虑边界条件
class Solution {public int coinChange(int[] coins, int amount) {// 例如总金额11,面额为1,2,5// 可能性1:10 + 1:凑面额10的最小数量 + 1// 可能性2: 9 + 2: 凑面额9的最小数量 + 1// 可能性3: 6 + 5: 凑面额6的最小数量 + 1// 结果为以上三种可能性的最大值// 特殊场景:case2,要特殊判定int f[] = new int[amount + 1];f[0] = 0;for (int i = 1; i <= amount; i++) {int min = Integer.MAX_VALUE;for (int j = 0; j < coins.length; j++) {int subIndex = i - coins[j];if (subIndex >= 0 && f[subIndex] != -1) {min = Math.min(min, f[subIndex] + 1);}}// fi赋值if (min == Integer.MAX_VALUE) {min = -1;}f[i] = min;}return f[amount];}
}
347. 前k个高频元素
https://leetcode.cn/problems/top-k-frequent-elements/?envType=study-plan-v2&envId=top-100-liked
思路:手动构建大根堆,大小按照出现次数来排, 同时构建结构体,存储每个数字值与出现次数
class Solution {public int[] topKFrequent(int[] nums, int k) {// 构建map,统计次数Map<Integer, Integer> map1 = new HashMap<Integer, Integer>();for (int i = 0; i < nums.length; i++) {if (map1.containsKey(nums[i])) {map1.put(nums[i], map1.get(nums[i]) + 1);} else {map1.put(nums[i], 1);}}// 构建nodesint len = map1.keySet().size();Node[] nodes = new Node[len];int t = 0;for (int key : map1.keySet()) {nodes[t++] = new Node(key, map1.get(key));}// 构建大根堆buildMaxHeap(nodes, len);int[] res = new int[k];for (int i = 0; i < k; i++) {res[i] = nodes[0].num;swap(nodes, 0, len - 1);len--;modifyMaxHeap(nodes, 0, len);}return res;}public void buildMaxHeap(Node[] nodes, int len) {for (int i = (len >> 1); i >= 0; i--) {modifyMaxHeap(nodes, i, len);} }public void modifyMaxHeap(Node[] nodes, int cur, int len) {if (cur >= len) {return;}int ocur = cur;int left = cur * 2 + 1;int right = cur * 2 + 2;if (left < len && nodes[left].count > nodes[cur].count) {cur = left;}if (right < len && nodes[right].count > nodes[cur].count) {cur = right;}if (ocur != cur) {swap(nodes, ocur, cur);modifyMaxHeap(nodes, cur, len);}}public void swap(Node[] nodes, int ocur, int cur) {Node t = nodes[ocur];nodes[ocur] = nodes[cur];nodes[cur] = t;}class Node {public int num;public int count;public Node(int num, int count) {this.num = num;this.count = count;}}}
415. 字符串相加——大数——一刷
模拟
class Solution {public String addStrings(String num1, String num2) {int i = num1.length() - 1, j = num2.length() - 1;int upNum = 0;String res = "";while (i >= 0 || j >= 0) {int n1 = (i >= 0 ? num1.charAt(i) - '0' : 0);int n2 = (j >= 0 ? num2.charAt(j) - '0' : 0);int tmpRes = (n1 + n2 + upNum) % 10;upNum = (n1 + n2 + upNum) / 10;res = ((char)(tmpRes + '0')) + res;i--; j--;}if (upNum > 0) {res = (char)(upNum + '0') + res;}return res;}
}
437. 路径总和三——树&DFS*
https://leetcode.cn/problems/path-sum-iii/submissions/569489131/?envType=study-plan-v2&envId=top-100-liked
解题思想:首先遍历每一个节点,然后对于每一个节点都执行dfs,即获取路径总和, 最后相加即可
DFS的设计思想:把他想象成是一次遍历即可,其他递归完成。
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {public int pathSum(TreeNode root, int targetSum) {// 首先要遍历所有节点,这个过程通过递归完成; 其次针对每个节点,都做一次DFSif (root == null) {return 0;}int res = 0;res = dfs(root, targetSum);res += pathSum(root.left, targetSum);res += pathSum(root.right, targetSum);return res;}public Integer dfs(TreeNode root, long targetSum) {if (root == null) {return 0;}int ret = 0;if (targetSum == root.val) {ret += 1;}ret += dfs(root.left, targetSum - root.val);ret += dfs(root.right, targetSum - root.val);return ret;}
}
438. 找到字符串中所有字母异位词——滑动窗口&异位词——一刷
https://leetcode.cn/problems/find-all-anagrams-in-a-string/?envType=study-plan-v2&envId=top-100-liked
滑动窗口裸题,核心在于异位词的判断,用char[],结合Arrays.equals
class Solution {public List<Integer> findAnagrams(String s, String p) {// set记录,滚动数组,如果不在里面,直接清空,并且进行下一次循环, 如果在里面,+1,进行下一次循环// 涉及到异位词的,直接排序伺候List<Integer> res = new ArrayList<>();char[] ss = s.toCharArray(), pp = p.toCharArray();int len1 = s.length(), len2 = p.length();int[] order = new int[26];for (int i = 0; i < len2; i++) {order[pp[i] - 'a']++;}int curLen = 0;int[] curNums = new int[26];for (int l = 0, r = 0; r < len1; r++) {int index = ss[r] - 'a';curNums[index]++;while (curNums[index] > order[index]) {curNums[ss[l] - 'a']--;l++;}if (Arrays.equals(order, curNums)) {res.add(l);}}return res;}
}
543. 二叉树的直径——递归——二刷
https://leetcode.cn/problems/diameter-of-binary-tree/?envType=study-plan-v2&envId=top-100-liked
解题思路:不论哪条路径最长,最长路径一定符合左子树长度+右子树长度+1的特征, 然后根据这个特征用DFS遍历深度即可。
class Solution {int res = 0;public int diameterOfBinaryTree(TreeNode root) {// 不论哪条路径最长,一定是左子树长度+右子树长度+1nodeDepth(root);return res - 1;}public int nodeDepth(TreeNode node) {if (node == null) {return 0;}int left = nodeDepth(node.left);int right = nodeDepth(node.right);res = Math.max(res, (left + right + 1));return Math.max(left, right) + 1;}
}
二刷:还是看了题解,推了一会才推出来,需要三刷
class Solution {int res = 0;public int diameterOfBinaryTree(TreeNode root) {// 每个节点的最大深度,可以理解为该节点左子树最大长度 + 右子树最大长度 + 1, 据此递归即可// 变量怎么保存? 用全局变量if (root == null) {return 0;}getMaxNum(root);return res - 1;}public int getMaxNum(TreeNode root) {if (root == null) {return 0;}int leftNum = getMaxNum(root.left);int rightNum = getMaxNum(root.right);res = Math.max(res, (leftNum + rightNum + 1));return Math.max(leftNum, rightNum) + 1;}
}
560. 和为k的子数组——前缀和*——二刷
https://leetcode.cn/problems/subarray-sum-equals-k/?envType=study-plan-v2&envId=top-100-liked
class Solution {public int subarraySum(int[] nums, int k) {// 维护一个前缀和数组, 如果i-j的连续子串和为k, 可以转换为j的前缀和-k=i的前缀和;// 把所有前缀和维护在map中,就可以o1的检测出是否有这个和了(这里要考虑前缀和出现多次的情况,用value来记录)Map<Integer, Integer> map = new HashMap<Integer, Integer>();// pre-k恰好为0的场景map.put(0, 1);int pre = 0;int res = 0;boolean first = true;for (int i = 0; i < nums.length; i++) {pre += nums[i];// 要先判断,再插入, 如果先put, k=0的场景下会多算if (map.containsKey(pre - k)) {res += map.get(pre - k);}if (map.containsKey(pre)) {map.put(pre, map.get(pre) + 1);} else {map.put(pre, 1);}}return res;}
}// 暴力
// class Solution {
// public int subarraySum(int[] nums, int k) {
// int len = nums.length;
// int[] sumList = new int[len];
// sumList[0] = nums[0];
// for (int i = 1; i < len; i++) {
// sumList[i] = sumList[i - 1] + nums[i];
// }// int res = 0;// for (int i = -1; i < len; i++) {
// for (int j = i + 1; j < len; j++) {
// int t = 0;
// if (i != -1) {
// t = sumList[i];
// }
// if (sumList[j] - t == k) {
// res++;
// }
// }
// }
// return res;
// }
// }
二刷:还是卡了很长时间,这里对+=的理解不够深刻
class Solution {public int subarraySum(int[] nums, int k) {// 滑动窗口没法处理负数场景Map<Integer, Integer> map = new HashMap<>();int pre = 0, res = 0;// put的作用是,可能出现数组自身的前缀和就是kmap.put(0, 1);for (int i = 0; i < nums.length; i++) {pre += nums[i];// += 的作用是,pre-k的前缀和出现了多少次,就有多少种构造结果,res就增加对应的数值if (map.containsKey(pre - k)) {res += map.get(pre - k);}// 每次遍历都记录这次遍历的前缀和的次数if (map.containsKey(pre)) {map.put(pre, map.get(pre) + 1);} else {map.put(pre, 1);}}return res;}
}
718. 最长重复子串——DP——一刷
https://leetcode.cn/problems/maximum-length-of-repeated-subarray/
// class Solution {
// public int findLength(int[] nums1, int[] nums2) {
// // DP思路: 当nums1[i] = nums2[j]时,dp[i][j] + dp[i-1][j-1]+1; ans取最大值, 时间复杂度Omn,空间复杂度Omn
// int len1 = nums1.length, len2 = nums2.length;
// int dp[][] = new int[len1 + 1][len2 + 1];
// int res = 0;
// for (int i = 1; i <= len1; i++) {
// for (int j = 1; j <= len2; j++) {
// if (nums1[i - 1] == nums2[j - 1]) {
// dp[i][j] = dp[i - 1][j - 1] + 1;
// res = Math.max(res, dp[i][j]);
// }
// }
// }
// return res;
// }
// }// 滚动数组, 其实每一个dp[i][j],只依赖dp[i-1][j-1],优化成一维,只突出j,每一次新的循环,dp数组都是上一次的值,这样就达到了滚动的效果
class Solution {public int findLength(int[] nums1, int[] nums2) {int len1 = nums1.length, len2 = nums2.length;int dp[] = new int[len2 + 1];int res = 0;for (int i = 1; i <= len1; i++) {for (int j = len2; j >= 1; j--) {if (nums1[i - 1] == nums2[j - 1]) {dp[j] = dp[j - 1] + 1;res = Math.max(res, dp[j]);} else {// 初始化dp[j] = 0;}}}return res;}
}
763. 划分字母区间——贪心*
https://leetcode.cn/problems/partition-labels/?envType=study-plan-v2&envId=top-100-liked
解题思路:贪心,见代码,只能说很巧妙,很好的贯彻了贪心的思想。
class Solution {public List<Integer> partitionLabels(String s) {List<Integer> res = new ArrayList<Integer>();// 1. 记录每一个字母最后一次出现的位置// 2. 如果该字母最后一次出现的位置之前,所有的字母,最后一次出现的位置,都在它之前,则证明是一个区间,反之,获取到更远的位置// 3. 总之每一次遍历,都是当下看的最优策略,即贪心char[] ss = s.toCharArray();int endIndex = 0;int[] endPosList = new int[26];for (int i = 0; i < ss.length; i++) {endPosList[ss[i] - 'a'] = i;}int sum = 0;for (int i = 0; i < ss.length; i++) {if (endPosList[ss[i] - 'a'] > endIndex) {endIndex = endPosList[ss[i] - 'a'];}if (i == endIndex) {res.add(i + 1 - sum);sum = i+1;}}return res;}
}
1143. 最长公共子序列——二维DP*
https://leetcode.cn/problems/longest-common-subsequence/?envType=study-plan-v2&envId=top-100-liked
解题思路:核心在于理解公式,具体看代码
class Solution {public int longestCommonSubsequence(String text1, String text2) {// 解题思路:对于a串,如果text1[i] = text2[j] ,则dp[i][j] = dp[i-1][j-1] + 1// 否则,dp[i][j] = max(dp[i-1][k], dp[i][j-1])int len1 = text1.length(), len2 = text2.length();int dp[][] = new int[len1 + 1][len2 + 1];// 这里要注意,遍历1-n,不是0-n-1,因为0是给长度为0的字符串准备的,不算在字符串长度遍历里char[] ss1 = text1.toCharArray(), ss2 = text2.toCharArray();for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {if (ss1[i-1] == ss2[j-1]) {dp[i][j] = dp[i-1][j-1] + 1;} else {dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);}}}return dp[len1][len2];}
}
三、大公司高频题
字节
四、高频题分类
链表高频题
PS:LRU好好练。。频率高的都快冒出来了,我本人也是多次遇到LRU,的确很考验算法功底,在面试较高压场景下,正确写出不容易
树高频题
PS:感觉树整体频率不高
回溯高频题
PS:整体出题频率也不太高
Hot 100 + 高频题的前三名: 全排列、分割回文串、N皇后、组数总和、子集
二分高频题
Hot 100的高频题:4、33、34、35、74
栈高频题
Hot 100中的高频题: