会统计一些思路在下面,就是做完题总结出“为什么我会想到用这个方法做?”
**2021/3/3留言:**最近没有再更新很多readme文档,主要是每次传完代码再来这里写有些麻烦,所以可以在src/top100文件夹下查看我总结出的高频100题(更新中),代码本身就添加了详细的注释,看完一定懂,欢迎发邮件交流[email protected]
题号 | 关键点 | 刷题指数 |
---|---|---|
剑指offer24 反转链表 | 双指针 | 🌟 |
69求平方根 | 二分法查找 | 🌟 |
剑指offer03 | hashset | 🌟 |
20有效括号 | 栈 | 🌟 |
155最小栈 | 栈 | 🌟 |
160多数元素 | hashmap | 🌟🌟 |
26删除排序数组的重复元素 | 双指针 | 🌟 |
136只出现一次的数字 | 位运算 | 🌟🌟🌟 |
344反转字符串 | 双指针 | 🌟 |
141判断链表有环 | 双指针,快慢指针 | 🌟🌟 |
14找最长公共前缀 | 字符串API | 🌟 |
9判断回文数 | 双指针 | 🌟 |
226 翻转二叉树 | 二叉树 | 🌟🌟🌟🌟🌟 |
114二叉树展开为链表 | 二叉树 | 🌟🌟🌟 |
116填充每个节点的下一个右侧节点指针 | 二叉树 | 🌟🌟🌟 |
92反转链表 | 双指针 递归也行 | 🌟🌟🌟 |
654创造最大二叉树 | 递归 | 🌟🌟🌟 |
652二叉树重复子树 | 二叉树 递归 | 🌟🌟🌟 |
230寻找第k小的元素 | 二叉树 递归 | 🌟🌟🌟 |
538BST转累加树 | BST 递归 | 🌟🌟🌟 |
98验证二叉搜索树 | BST递归 | 🌟🌟🌟 |
700搜索二叉树中的搜索 | BST递归 | 🌟🌟🌟 |
701搜索二叉树插入 | BST递归 | 🌟🌟🌟 |
450删除搜索二叉树某个元素 | BST递归 | 🌟🌟🌟🌟 |
297二叉树的序列化于反序列化 | 二叉树 递归 | 🌟🌟🌟🌟 |
236最近公共祖先 | 二叉树 递归 | 🌟🌟🌟 |
222求完全二叉树的节点数 | 二叉树 递归 完全二叉树 满二叉树 | 🌟🌟🌟 |
146LRU缓存机制 | 设计题 | 🌟🌟🌟🌟🌟 |
496下一个更大元素 | 单调栈 | 🌟🌟🌟 |
739每日温度 | 单调栈 | 🌟🌟🌟 |
503环形数组中下一个更大元素 | 单调栈 | 🌟🌟🌟 |
239滑动窗口最大值 | 单调栈 双端队列 | 🌟🌟🌟🌟 |
232用栈实现队列 | 栈 | 🌟 |
875猪b吃香蕉 | 二分法 | 🌟🌟🌟 |
876求链表中间节点 | 快慢指针 | 🌟 |
19删除倒数第n个节点 | 快慢指针 | 🌟 |
142求有环链表的环起点(背下来!) | 快慢指针 | 🌟🌟🌟🌟🌟 |
509斐波那契额数列 | 动态规划 | 🌟🌟🌟🌟🌟 |
剑指offer03 | ||
剑指offer09 | ||
剑指offer25 | ||
剑指offer48 | ||
剑指offer07 | ||
剑指offer40 | ||
剑指offer50 | ||
剑指offer55 | ||
剑指offer45 | 自定义排序 | 🌟🌟 |
剑指offer04 | 观察出B树就能做 | 🌟🌟 |
剑指offer06 | 栈做的 | 🌟🌟 |
剑指offer32 | 二叉树的层序遍历 | 🌟🌟🌟🌟🌟 |
剑指offer12 | ||
03无重复的最长子串 | 滑动窗口 | 🌟🌟🌟🌟🌟 |
76最小覆盖子串 | 滑动窗口 | 🌟🌟🌟🌟🌟 |
剑指offer67 | 字符串转整数 | 🌟🌟🌟🌟🌟 |
215 数组中的第K个最大元素 | 优先队列 | 🌟🌟🌟🌟🌟 |
695、200两个岛屿问题 | DFS | 🌟🌟🌟🌟🌟 |
// 反转以 a 为头结点的链表
ListNode reverse(ListNode a) {
ListNode pre, cur, nxt;
pre = null; cur = a; nxt = a;
while (cur != null) {
nxt = cur.next;
// 逐个结点反转
cur.next = pre;
// 更新指针位置
pre = cur;
cur = nxt;
}
// 返回反转后的头结点
return pre;
}
//反转a到b之间的链表 注意左闭右开
ListNode kReverse(ListNode a, ListNode b) {
ListNode pre, nxt;
pre = null;
while (a != b) {
nxt = a.next;
a.next = pre;
pre = a;
a = nxt;
}
return pre;
}
其实第一个反转全部链表相当于,从反转头指针到null之间的链表,这样第二种就很好理解
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
// 前序遍历
traverse(root.left)
// 中序遍历
traverse(root.right)
// 后序遍历
}
二叉树的所有题,都离不开这个递归模板,只是具体操作放在哪的问题,在前序还是中序还是后序
void traverse(TreeNode root) {
if (root == null) return;
traverse(root.left);
// 中序遍历代码位置
print(root.val);
traverse(root.right);
}
如果想降序
void traverse(TreeNode root) {
if (root == null) return;
// 先递归遍历右子树
traverse(root.right);
// 中序遍历代码位置
print(root.val);
// 后递归遍历左子树
traverse(root.left);
}
boolean isInBST(TreeNode root, int target) {
if (root == null) return false;
if (root.val == target) return true;
// 当前节点没找到就递归地去左右子树寻找
return isInBST(root.left, target)
|| isInBST(root.right, target);
}
boolean isInBST(TreeNode root, int target) {
if (root == null) return false;
if (root.val == target)
return true;
if (root.val < target)
return isInBST(root.right, target);
if (root.val > target)
return isInBST(root.left, target);
// root 该做的事做完了,顺带把框架也完成了,妙
}
搜索就是找到这个数,找到了就可以对这个数进行操作,类似更改,删除,添加。
void BST(TreeNode root, int target) {
if (root.val == target)
// 找到目标,做点什么
if (root.val < target)
BST(root.right, target);
if (root.val > target)
BST(root.left, target);
}
如果涉及插入,需要有返回值,模板如下
TreeNode insertIntoBST(TreeNode root, int val) {
// 找到空位置插入新节点
if (root == null) return new TreeNode(val);
// if (root.val == val)
// BST 中一般不会插入已存在元素
if (root.val < val)
root.right = insertIntoBST(root.right, val);
if (root.val > val)
root.left = insertIntoBST(root.left, val);
return root;
}
- 这个函数是干嘛的
- 这个函数参数重的变量是什么(一直在变化的是啥)
- 得到函数的递归结果,你该干什么
以“求每个数的右侧最大数”为例
public int[] nextGreaterElement(int[] nums) {
int[] answerArr = new int[nums.length];
Stack<Integer> stack = new Stack<>();
//从后向前遍历
for (int i = nums.length - 1; i >= 0; i--) {
//栈不是空的
while (!stack.isEmpty() && stack.peek() <= nums[i]) {
stack.pop();
}
//如果栈不空但 栈顶元素比当前元素大
answerArr[i] = stack.isEmpty() ? -1 : stack.peek();
//就把这个数放进去
stack.push(nums[i]);
}
return answerArr;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) { // 注意
int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
上面是第一种:找到了就返回,正中间那个数
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = (left + right) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
上面是第二种,有重复值的中间值,找左侧边界,比如【1,2,3,3,3,3,4】
int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1; // 注意
}
上面是有重复值时候找右侧边界。
觉得有必要的再具体写几句
这题没啥难度没啥好总结的
这题主要复习一下str.charAt() str.substring()
很经典,快慢指针,没难度,注意coding细节
难度不大,但是处理思想和两个链表数相加比较相似,值得总结
惊了!位运算也太骚了看官方解答
两分钟AC但是性能不好,解答中的分治算法可以看看
双指针太浪漫了,思路值得学习
对于数组的问题,常用的方法是双指针,首先为什么要用双指针,就是为了把已处理和未处理的数组元素区分开,也就是说通过两个指针,把数组分成3个部分。对于同向双指针,通常[0...i)(注意看题目要求是闭区间还是开区间)表示已处理,[i,j)还未处理,只要明白双指针的本质是为了区分已处理和未处理,就能轻松写出代码,但需要注意区间是闭区间还是开区间。
题解区的这一段话值得仔细思考,数组的题我们应该首先想想双指针是否能做
而且226的翻转二叉树虽然不难,但是他有重要的战略意义,就算是背也得背下来
双指针方法反转链表也有着同样的战略意义
这道题递归思想很重要
需要截取数组时候不一定非要真的截取出来,可以传index,按照首尾index来取。这样不用额外的空间。
这道题很典型体现递归在二叉树中的应用,并且很练coding,建议多做几遍。
多做几遍,非常练习coding
有点难理解,多看几遍有空
这题面试总是出现,最好是自己实现双向链表不要用LinkedHashMap
模板记住!
当需要循环数组的时候,我们的思想应该是 将数组长度变为二倍,再复制一个完整的接到末尾
但实际操作不需要复制数组,*只需要将 for循环的 index 2 就行
二叉树的前中后遍历和层序必须背下来好吗
这道题超级无敌高频面试出现,我在代码里写了详细注释
很难很经典的滑动窗口,但是常考,思路上不难,但coding难
全是细节,思路不难 全是coding细节,常考 字节跳动题库里的