2026-10-19学习笔记

环形链表

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

思路:

第一阶段:快慢指针先找相遇点

第二阶段:头指针和相遇节点指针一起移动,直到移到相同点,这个相同点就是环入口

推导:

一、先定义好三个"物理距离"

假设链表长这样(E 是环入口,M 是快慢指针相遇点):

text

head ───a───> E ───b───> M

↑ │

└─────c─────┘

  • a :从头节点 head 走到环入口 E 的距离。

  • b :从环入口 E 走到相遇点 M 的距离(沿环走的方向)。

  • c :从相遇点 M 继续走到环入口 E 的距离(沿环走的方向)。

  • 环的总长 = b + c。


二、快慢指针各走了多远?

假设相遇时:

  • 慢指针走了:a + b

  • 快指针走了:a + b + n(b + c),其中 n ≥ 1 是快指针在环里多转的圈数。

因为快指针速度是慢指针的 2 倍,所以在相同时间内,快指针走的路程是慢指针的 2 倍:

a+b+n(b+c)⏟快指针=2⋅(a+b)⏟慢指针快指针a+b+n(b+c)​​=2⋅慢指针(a+b)​​

化简:

a+b+n(b+c)=2a+2ba+b+n(b+c)=2a+2bn(b+c)=a+bn(b+c)=a+ba=(n−1)(b+c)+ca=(n−1)(b+c)+c​


三、关键:如何"读"这个等式?

把等式右边拆开看:

a=(n−1)(b+c)+c⏟​

这句话的物理含义是:

从头节点走 a 步到环入口,等价于------从相遇点 M 出发,先绕环转 n-1 整圈,再走 c 步,同样也会到达环入口 E。

也就是说,a 和 c 这两段路,终点是同一个地方(环入口 E),只是起点不同:

  • 从 head 出发走 a 步 → 到达 E

  • 从 M 出发走 c 步(绕若干圈后)→ 到达 E

/**

* Definition for singly-linked list.

* class ListNode {

* int val;

* ListNode next;

* ListNode(int x) {

* val = x;

* next = null;

* }

* }

*/

public class Solution {

public ListNode detectCycle(ListNode head) {

if(head==null)return null;

ListNode slow=head;

ListNode fast=head;

while(fast!=null&&fast.next!=null){

slow=slow.next;

fast=fast.next.next;

if(slow==fast){

ListNode meet=slow;

ListNode h1=head;

while(h1!=meet){

h1=h1.next;

meet=meet.next;

}

return meet;

}

}

return null;

}

}

二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

思路:BFS,开一个结果数组和队列,队列存储每一行的所有节点,对于每次遍历,当遍历到当前行最后一个节点时,将当前节点加入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 rightSideView(TreeNode root) {

Listres=new ArrayList<>();

Queue queue=new LinkedList<>();

if(root==null)return res;

queue.offer(root);

while(!queue.isEmpty()){

int n=queue.size();

for(int i=0;i<n;i++){

TreeNode node=queue.poll();

if(i==n-1){

res.add(node.val);

}

if(node.left!=null){

queue.offer(node.left);

}

if(node.right!=null){

queue.offer(node.right);

}

}

}

return res;

}

}

pdd面经

2.JAVA面向对象的三个特征。

封装

把数据(属性)和操作数据的方法(行为)捆绑在类里,对外只暴露必要接口,隐藏内部实现细节。好处是降低耦合、保护数据完整性、便于修改实现而不影响调用方。

继承

子类复用父类的属性和方法,表达「is-a」关系,实现代码复用和多态的基础。注意:Java 是单继承,但可通过接口实现多继承语义

多态 同一接口/父类引用指向不同子类对象,运行时表现出不同行为(动态绑定)。前提是继承 + 方法重写 + 父类引用指向子类对象。

多态 = 声明类型统一(父类/接口),实际类型各异(子类对象),方法调用在运行时按实际类型动态分派,从而用同一段调用代码产生不同行为。

1. 「同一接口/父类引用」

Animal a1 和 Animal a2 的变量类型 是 Animal。编译器在编译期只认 Animal 这个类型,它只知道「有个 speak() 方法」,不知道也不关心具体是狗还是猫。

2. 「指向不同子类对象」

new Dog() 和 new Cat() 在堆上创建了不同的对象 ,a1、a2 分别持有它们的引用。也就是说:引用的静态类型相同,但指向的运行时对象不同。

3. 「运行时表现出不同行为」+「动态绑定」

关键问题来了:a1.speak() 编译时,编译器看到的是 Animal.speak(),它怎么知道要调用 Dog 的实现?

答案是方法调用的绑定发生在运行时,而不是编译时:

  • 编译期:只检查 Animal 有没有 speak(),有就通过(编译通过)。

  • 运行期:JVM 看 a1 实际指向的对象 是 Dog,于是去 Dog 的方法表里找 speak() 的实现来执行。

这个「运行时根据实际对象类型决定调用哪个方法」的机制,就叫动态绑定(动态分派)。JVM 底层通常通过**虚方法表(vtable)**实现:每个类有一张方法表,子类重写的方法会覆盖父类对应槽位,调用时按实际对象的方法表取入口。

4. 「前提是继承/实现 + 方法重写 + 父类引用指向子类对象」

这三个是缺一不可的条件:

条件 作用 缺了会怎样
继承/实现 建立「is-a」关系,子类才能被父类引用指向 Animal a = new Dog() 编译报错,类型不兼容
方法重写 子类提供自己的实现 不重写就继承父类实现,调用结果都一样,没有「不同行为」
父类引用指向子类对象 让「声明类型」和「实际类型」分离 若写 Dog d = new Dog(),编译期就绑定了 Dog,没有动态分派可言

三者合起来,才制造出「同一个调用,不同对象,不同结果」的效果。

多态的实际价值(为什么设计它)

没有多态,你只能写:

java

if (animal instanceof Dog) ((Dog) animal).speak();

else if (animal instanceof Cat) ((Cat) animal).speak();

每加一种动物就要改这段 if-else,违反开闭原则。有了多态,调用方只写 animal.speak(),新增子类无需改动调用方代码------这就是「面向接口编程」

3.反射是什么?优缺点是什么?

反射是程序在运行时 动态获取类的信息(类名、方法、字段、构造器、注解等)并操作对象的能力。核心入口是 Class 对象,常用 API:Class.forName()、getDeclaredMethods()、newInstance()、setAccessible(true) 等。Spring 的 IOC/DI、MyBatis 映射、动态代理都大量依赖反射。

优点

  • 运行时动态性:框架可以在不知道具体类的情况下装配、调用。

  • 解耦:配置驱动,扩展性强。

  • 能突破访问限制(setAccessible),实现通用逻辑。

缺点

  • 性能开销 :方法调用比直接调用慢(有安全检查、无法内联优化),可通过缓存 Method 对象、MethodHandle 缓解。

  • 安全性:可绕过访问控制,破坏封装;

  • 可读性/调试难:错误在运行时才暴露,编译期无法检查。

4.介绍一下数据库中的索引。

索引是帮助数据库高效检索数据的数据结构,本质是「排好序的、指向数据行的辅助结构」,用空间换时间。

常见类型

  • 按结构:B+ 树索引(MySQL InnoDB 默认)、哈希索引、全文索引、R-Tree(空间)。

  • 按逻辑:主键索引(聚簇)、唯一索引、普通索引、联合索引、前缀索引。

  • 按物理:聚簇索引(数据即索引,InnoDB 主键)、二级索引(叶子存主键,需回表)。

关键概念

  • 最左前缀原则 :联合索引 (a,b,c) 能命中 a、a,b、a,b,c,不能跳过 a 直接用 b。

  • 回表 :二级索引查到主键后再回聚簇索引取整行;覆盖索引可避免回表。

  • 索引失效 :对索引列做函数运算、隐式类型转换、like '%x' 前导模糊、or 连接非索引列等。

代价:占空间、增删改要维护索引、降低写性能。

我的思考 :索引不是越多越好,要结合查询模式设计。联合索引的顺序要按「等值列在前、范围列在后」排;写多读少的表要克制建索引;用 explain 看 type、key、rows 验证是否命中。

5.B+如何实现范围查询?

  1. 所有数据都在叶子节点,非叶子节点只存索引键,起导航作用------同样磁盘页能存更多键,树更矮,IO 更少。

  2. 叶子节点之间用双向链表连接,且按键有序。

6.volatile和synchronized的区别

7.介绍一下JAVA里面的集合。

8.hashmap扩容

9.concurrenthashmap安全性

10.CAS和加锁

11.什么是悲观锁?什么是乐观锁?

12.jvm介绍

相关推荐
金色光环1 小时前
【概率论详细笔记】泊松分布是怎么来的?应该怎么用?
笔记·概率论
又見山1 小时前
双向链接的本质:链接不是功能,是思考留下的痕迹
笔记·obsidian
云水初1 小时前
【agent篇】RAG Agent避坑指南(附高级RAG Agent汇总)
开发语言·笔记·python·学习·langchain·agent·rag
外收内放2 小时前
数据分析(图表信息详解)
python·学习·数据分析·matplotlib
气质、小青年!2 小时前
进程1学习
linux·学习
JasmineX-12 小时前
数据结构(笔记)——顺序栈
c语言·数据结构·笔记
坤坤子吖2 小时前
Linux 线程同步与互斥——互斥锁
linux·服务器·c++·笔记·学习
撩得Android一次心动2 小时前
Jetpack Compose 知识点整理3【个人用】
android·笔记·学习·kotlin·android compose
威嵌神州3 小时前
国产化加固计算机项目选型笔记|板卡、整机、平板与定制模块评估要点
笔记·fpga开发·电脑