螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
思路:模拟移动,定义4个边界,top,bottom,left,right
-
top:上边界,初始为0。 -
bottom:下边界,初始为m - 1。 -
left:左边界,初始为0。 -
right:右边界,初始为n - 1。
整个遍历过程分为四个动作,循环执行,直到边界相撞:
-
向右走 :从
left走到right,走完后,上边界top向下缩一格(top++)。 -
向下走 :从
top走到bottom,走完后,右边界right向左缩一格(right--)。 -
向左走 :从
right走到left,走完后,下边界bottom向上缩一格(bottom--)。 -
向上走 :从
bottom走到top,走完后,左边界left向右缩一格(left++)。
核心易错点(防重复)
在向下走和向上走之前,必须 加一个条件判断(if (top <= bottom) 和 if (left <= right))。
为什么?因为如果矩阵不是正方形(比如只有一行,或者只有一列),走到最后可能会原路返回,导致元素被重复加入结果集。加了这个判断,就可以保证只有在空间还没被榨干时才继续。
class Solution {
public List spiralOrder(int\[\]\[\] matrix) {
List res=new ArrayList<>();
if(matrix==null)return res;
int n=matrix.length;
int m=matrix0.length;
int top=0;
int bottom=n-1;
int left=0;
int right=m-1;
while(top<=bottom&&left<=right){
for(int i=left;i<=right;i++){
res.add(matrixtopi);
}
top++;
for(int i=top;i<=bottom;i++){
res.add(matrixiright);
}
right--;
if(top<=bottom){
for(int i=right;i>=left;i--){
res.add(matrixbottomi);
}
bottom--;
}
if(left<=right){
for(int i=bottom;i>=top;i--){
res.add(matrixileft);
}
left++;
}
}
return res;
}
}
环形链表
判断有无环形
思路:快慢指针是否会重合
/**
* 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)return false;
ListNode slow=head;
ListNode fast=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
return true;
}
}
return false;
}
}
相交链表
两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
思路:a,b指针走到null后分别再重头走对方的路直到有相交,如果有则返回相交的点
最优雅的解法是双指针法。我们可以把它想象成两个人在走迷宫:
-
指针
pA在链表 A 上走,走到底后,去走链表 B。 -
指针
pB在链表 B 上走,走到底后,去走链表 A。
为什么这样能相遇?
假设链表 A 的独立部分长度为 a,链表 B 的独立部分长度为 b,相交部分的长度为 c。
-
pA走过的总路程是:a + c + b(先走完 A,再走 B 的独立部分)。 -
pB走过的总路程是:b + c + a(先走完 B,再走 A 的独立部分)。
你会发现,两者的总路程完全相等 (都是 a + b + c)。
所以,如果两个链表有交点,它们一定会同时到达交点 。
如果两个链表没有交点(即 c = 0),它们会同时走到对方的终点(null),此时返回 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) {
if(headAnull||headBnull)return null;
ListNode pa=headA;
ListNode pb=headB;
while(pa!=pb){
pa=(pa==null)?headB:pa.next;
pb=(pb==null)?headA:pb.next;
}
return pa;
}
}
回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
思路:利用快慢指针先找到中点->反转后半段链表->依此比较前半段链表和反转后的后半段链表值
设计:slow和fast,反转链表
/**
* 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 reverse(ListNode head){
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
public boolean isPalindrome(ListNode head) {
if(headnull||head.nextnull)return true;
ListNode slow=head;
ListNode fast=head;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
ListNode reverp=reverse(slow.next);
ListNode p1=head;
ListNode p2=reverp;
boolean flag=true;
while(p2!=null){
if(p1.val!=p2.val){
flag=false;
break;
}
p1=p1.next;
p2=p2.next;
}
return flag;
}
}
细节:fast.next!=null&&fast.next.next!=null 只有这样slow才能停到中点位置
用友
3.HashMap的底层原理,介绍一些put流程
底层原理:
JDK 8 以后,HashMap 底层是数组 + 链表 + 红黑树。数组(哈希桶)是主干,通过哈希算法定位。当发生哈希冲突时,以链表形式存储;当链表长度大于 8 且数组长度大于等于 64 时,链表会转化为红黑树,将查询时间复杂度从 O(n) 降为 O(log n)。
put 流程(核心要点):
-
计算哈希 :调用
hash(key)。 -
计算下标:通过
(n - 1) & hash计算数组下标(等价于取模,但位运算更快)。 -
处理冲突:
-
若桶为空,直接新建 Node 节点放入。
-
若桶不为空:比较 hash 和 key(
==或equals)。如果完全相等,则覆盖旧值。 -
如果不相等,判断是链表还是红黑树。是红黑树则插入树中;是链表则尾插法插入。插入后如果链表长度达到 8,触发树化逻辑。
-
-
扩容判断 :插入成功后,如果
size > threshold(容量 * 0.75 负载因子),则触发扩容(翻倍)。
4.SpringBean的生命周期,SpringBean有几种实例化方式
生命周期(四大阶段):
-
实例化(Instantiation):通过反射调用构造器创建 Bean 实例。
-
属性赋值(Population):依赖注入(DI),填充属性。
-
初始化(Initialization):
-
调用
BeanNameAware、BeanFactoryAware等 Aware 接口。 -
执行
BeanPostProcessor的postProcessBeforeInitialization。 -
执行
@PostConstruct注解的方法,或实现InitializingBean的afterPropertiesSet,或自定义的init-method。 -
执行
BeanPostProcessor的postProcessAfterInitialization(AOP 动态代理通常在这里生成)。
-
-
销毁(Destruction) :容器关闭时,执行
@PreDestroy、DisposableBean的destroy或自定义destroy-method。 -
构造器实例化(最常见):Spring 底层通过反射调用类的无参或带参构造函数。
-
静态工厂实例化 :调用某个类的
static静态方法返回对象。 -
实例工厂实例化:先实例化工厂类本身,再调用该工厂实例的普通方法返回对象。
-
FactoryBean 实例化 :调用
FactoryBean的getObject()方法返回对象。
5.方法重载和方法重写的区别
-
重载(Overload) :发生在同一个类 中,方法名相同,但参数列表不同 (类型、个数、顺序)。它属于编译期多态(静态绑定)。
-
重写(Override) :发生在子父类之间 (或接口实现),方法名、参数列表、返回类型必须相同。它属于运行期多态(动态绑定)。
资深视角理解: 重载是为了提供同名但不同输入的处理逻辑,提升 API 易用性;重写是为了多态扩展,让框架能面向接口编程。
6.==和equals的区别
-
==:如果是基本类型,比较数值;如果是引用类型,比较内存地址(是否指向同一个对象)。 -
equals:是Object类的方法,默认实现就是==。但像String、Integer等类重写了该方法,用来比较内容。 -
规范 :重写
equals必须同时重写hashCode,保证equals相等的两个对象hashCode也相等,否则会导致HashMap等集合出错。
7.Java常用集合类和他们的特点
-
List(有序、可重复):
-
ArrayList:基于动态数组,查快改慢(涉及数组复制),线程不安全。 -
LinkedList:基于双向链表,增删快,查慢,也可作队列。
-
-
Set(无序、不可重复):
-
HashSet:基于HashMap实现,无序。 -
LinkedHashSet:基于LinkedHashMap,维护插入顺序
-
-
Map(键值对):
-
HashMap:数组+链表+红黑树,无序,线程不安全,性能高。 -
ConcurrentHashMap:线程安全,分段/CAS 优化,高并发首选。Collection (接口) │┌─────────────────────┼─────────────────────┐
│ │ │
List (接口) Set (接口) Queue (接口)
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
ArrayList LinkedList HashSet TreeSet ArrayDeque LinkedList
(Vector) (LinkedHashSet) (PriorityQueue)
Map (接口) <-- 独立于 Collection 的另一大顶层接口 │┌───────────────────────┼───────────────────────┐
│ │ │
HashMap TreeMap Hashtable
│
LinkedHashMap
(ConcurrentHashMap 等并发包下的类)
-
8.List和Set的区别是什么
-
重复性 :
List允许重复元素,Set不允许(通过equals和hashCode判重)。 -
有序性 :
List是有序的(元素存入和取出的顺序一致,有索引),Set是无序的(HashSet无序,TreeSet按大小排序,LinkedHashSet按插入顺序)。 -
索引访问 :
List支持通过下标get(i)访问,Set不支持。
9.Map怎么用,hashMap是有序的吗,有序的Map是哪个
-
用法 :存储键值映射。遍历通常用
entrySet();线程安全场景用ConcurrentHashMap。 -
HashMap 无序。
-
有序的 Map:
-
需要按插入顺序 存取:用
LinkedHashMap(底层加了双向链表)。 -
需要按 Key 大小排序 :用
TreeMap(底层红黑树)。
-
10.抽象类和接口的区别
-
抽象类:单继承,可以有构造函数、普通变量、普通方法,是对事物的抽象。
-
接口:多实现,是对行为的规范。
-
资深视角:优先使用接口,因为组合优于继承。只有在需要共享代码实现或维护一个稳定骨架时,才用抽象类(模板方法模式)。。
11.你是怎么理解IOC的
本质上是一种设计思想 ,把创建和管理对象的控制权,从业务代码移交给了 Spring 容器。以前是自己 new 对象(主动权在自己),现在告诉容器"我需要什么",容器在运行时把它"注入"进来(DI,依赖注入)。核心价值 :解耦。如果实现类换了(比如从 MySQL 换到 Oracle),只需要改配置,不需要改业务代码。
13.说一下你对SQL优化的理解
-
索引优化(最见效):
-
使用
explain看执行计划,确保命中索引(type不为ALL,key不为 null)。 -
遵守最左前缀原则,尽量使用覆盖索引(避免回表)。
-
避免索引失效(如对索引列做函数运算、隐式类型转换、前导模糊查询
like '%x')。
-
-
SQL 语句重写:
- 避免
select *,只取需要的列。
- 避免