240. 搜索二维矩阵 II
要点:二分
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//二分
int n = matrix.length;
int m = matrix[0].length;
for(int i = 0; i < n; i++){
if(target < matrix[i][0]){
return false;
}
if(target > matrix[i][m-1]){
continue;
}
int left = 0;
int right = m-1;
while(left <= right){
int mid = left + (right - left)/2;
if(matrix[i][mid] == target){
return true;
}else if(matrix[i][mid] > target){
right = mid -1;
}else{
left = mid + 1;
}
}
}
return false;
}
}
98. 验证二叉搜索树
要点:中序遍历,pre
java
/**
* 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 boolean isValidBST(TreeNode root) {
//中序遍历
Deque<TreeNode> stack = new ArrayDeque<>();
//stack.push(root);
Long pre =- Long.MAX_VALUE;
while(!stack.isEmpty() || root != null){
while(root!= null){
stack.push(root);
root = root.left;
}
TreeNode node = stack.pop();
if(pre >= node.val){
return false;
}
pre = (long)node.val;
root = node.right;
}
return true;
}
}
144. 二叉树的前序遍历
要点:栈,pushleft,pop, right
java
/**
* 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> preorderTraversal(TreeNode root) {
//前序遍历->栈
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> stack = new ArrayDeque<>();
while(!stack.isEmpty() || root != null){
while(root!= null){
ans.add(root.val);
stack.push(root);
root = root.left;
}
TreeNode node = stack.pop();
// ans.add(node.val);
root = node.right;
}
return ans;
}
}
662. 二叉树最大宽度
要点:层次+编号
javascript
/**
* 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 widthOfBinaryTree(TreeNode root) {
//队列
Deque<TreeNode> queue = new LinkedList<>();
//编号
LinkedList<Integer> list = new LinkedList<>();
int max = 1;
queue.offer(root);
list.add(1);
while(!queue.isEmpty()){
int size = queue.size();
//max = Math.max(size, max);
for(int i = 0; i < size; i++){
TreeNode tmep = queue.poll();
Integer curInt = list.removeFirst();
if(tmep.left != null){
queue.offer(tmep.left);
list.add( curInt *2);
}
if(tmep.right != null){
queue.offer(tmep.right);
list.add(curInt *2 +1);
}
}
if(list.size() >= 2){
max = Math.max(max, list.getLast() - list.getFirst() + 1);
}
}
return max;
}
}
随机知识
索引的底层实现(追问高频)
为什么 MySQL InnoDB 选择 B+ 树而不是哈希表或二叉树?
面试官为什么这么问?
这道题把数据结构的课内知识和工程选型串起来。我要看你能不能结合磁盘 IO 和实际查询场景来解释选型原因,而不是只会背"B+ 树好"。
希望听到怎样的回答:
- 二叉树:数据量大时树太深,每次查都可能多次磁盘 IO,不适合磁盘存储。
- 哈希表:等值查询 O(1) 快,但不支持范围查询、排序,无法利用索引做 ORDER BY 或 BETWEEN。
- B+ 树:每个节点能存多个键值,树的高度非常低(通常 3-4 层),极大减少磁盘 IO;叶子节点有序双向链表,支持高效范围扫描和排序。这正是 MySQL 最常见的查询类型(范围查、排序、分页)。
候选人 :
好的,这个问题本质上是在问"数据库索引这种数据结构,核心要解决什么问题"。我按哈希表、二叉树、B树、B+树的顺序逐一分析,说明为什么 B+ 树是最优选择。
第一,为什么不用哈希表。
哈希表的最大特点是等值查询极快,时间复杂度 O(1),理论上单条数据查询性能比 B+ 树更好。MySQL 中也确实提供了哈希索引,在 Memory 引擎里可用,InnoDB 的自适应哈希索引也会自动构建哈希索引来加速热点页面的查询。
但哈希表作为通用索引结构有四个致命缺陷:
不支持范围查询。 WHERE id > 10 AND id < 100 这种条件,哈希表完全无法处理。因为哈希值之间没有顺序关系,数据被散列在各个桶里,彼此没有任何排序关联,只能全表扫描。
不支持排序和分组。 ORDER BY 和 GROUP BY 需要数据有序排列,哈希表无法提供顺序遍历能力,必须额外排序,开销巨大。
哈希冲突问题。 数据量大的时候,哈希冲突不可避免。冲突严重时,桶里的链表变长,查询退化到 O(n)。虽然可以通过扩容缓解,但扩容需要重新计算所有 key 的哈希值并迁移数据,代价高昂。
内存与磁盘的适配性差。 哈希表设计更适合内存场景,数据均匀随机分布。数据库索引存储在磁盘上,随机 IO 的性能远低于顺序 IO,哈希表这种随机访问模式在磁盘上性能很差。
所以哈希表只在特定的等值查询场景下有优势,无法胜任 MySQL 最常用的范围查询、排序、分组等需求。
第二,为什么不用二叉树。
二叉树结构简单,但数据存储在磁盘时,它有严重的性能问题。
数据库数据以页为单位存储在磁盘上,每个节点保存在不同页中,访问一个节点就是一次磁盘 IO。二叉树每个节点只有两个子节点,1000 万条数据,树的高度至少是 log₂(10⁷) ≈ 24 层。一次查询需要 24 次磁盘 IO,性能完全不可接受。
即使使用 AVL 或红黑树这类平衡二叉树,每个节点依然只有两个子节点,树的高度问题没有本质改善。二叉树的"矮胖"程度受限于每个节点只能存一个键值,磁盘 IO 次数直接和树高成正比,这就是二叉树不适合磁盘存储的根本原因。
第三,B 树的改进与不足。
B 树是多路平衡树,每个节点可以包含多个键值,拥有多个子节点。这让树变得非常"矮胖",高度极大降低。1000 万数据用 B 树,每个节点存几百个键值,高度只需 3~4 层,磁盘 IO 锐减。
但 B 树有一个关键设计:数据分散在所有节点中,非叶子节点既存索引也存数据行。每条数据行可能几百甚至上千字节,而索引键通常只有几个字节。非叶子节点存了数据行之后,能容纳的键值数量剧烈减少,树的分叉数降低、高度增加。而且范围查询时需要在各层之间跳来跳去,效率不够高。
第四,B+ 树的最终胜出。
B+ 树在 B 树基础上做了两个关键优化,精准匹配数据库需求。
优化一:非叶子节点只存键值,不存数据。 所有数据全部下沉到叶子节点。非叶子节点只承担"路标"作用,每个 16KB 的节点页可以放入大量键值(比如一个 bigint 键值 8 字节加指针 6 字节,一个节点可存上千个键值),树的分叉数极大,高度极低。千万级数据 3 层完全覆盖,一次查询 1~3 次磁盘 IO 就能完成。
优化二:叶子节点形成有序双向链表。 所有数据按键值顺序存在叶子节点中,用双向指针串联。等值查询快速定位到目标节点,范围查询只需沿链表顺序遍历,磁盘顺序 IO 性能极高,完美支持 BETWEEN、ORDER BY、GROUP BY、分页查询这些 MySQL 最常见的操作。
在 InnoDB 里,聚簇索引的 B+ 树叶子节点直接存放完整行数据,一个索引就包含了全部字段,避免了回表。非聚簇索引的叶子节点存放索引列的值加主键值,查到主键后再回聚簇索引取整行数据。
另外,InnoDB 还有自适应哈希索引机制:当某些数据页的访问模式显示等值查询非常频繁,InnoDB 会在内存中为这些页自动建哈希索引,后续查询直接走哈希 O(1),完全自动无需手动配置。这是对 B+ 树的补充,体现了工程优化。
第五,一个实际场景说明。
面试官追问时,可以用项目例子佐证。用户表的手机号字段建了唯一索引,Hash 索引能快速判定手机号是否已存在,但这不够------业务里还需要查出该手机号的用户详细信息和最近登录时间,要求范围查询或排序,只有 B+ 树可以同时支撑。订单列表按时间范围查询并排序分页,也是完全依赖 B+ 树的叶子节点双向链表来完成高效的顺序扫描和翻页。
总结一句话:哈希表等值查询快但不支持范围和排序,二叉树太深磁盘 IO 高,B 树数据分散导致分叉不足,B+ 树通过非叶子只存键值和叶子双向链表两重优化,让树极矮且支持高效顺序扫描,完美匹配 MySQL 磁盘 IO 和范围查询的多重需求。
碎碎念:后续会更新每天学习的八股和算法 题,暑假实习找不到了,开始准备秋招的第7天。努力连续更新100天!今天确实又没咋学,感觉一放假就不能正常学习,要坚持鸭,不要自己放纵自己。多坚持一下哇!!!!不管心情怎么样,都要按计划做。