链表专题
链表是数据结构中的"常青树",也是面试中绕不开的经典考点。本专题将带你从基础操作入手,逐步攻克反转、回文判断、环检测等高频难题,重点剖析快慢指针等核心技巧,助你彻底掌握链表的精髓,为后续学习更复杂的数据结构打下坚实基础。
文章目录
160. 相交链表
java
/**
* 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) {
Set<ListNode>visited = new HashSet<ListNode>();
ListNode temp = headA;
while (temp != null) {
visited.add(temp);
temp = temp.next;
}
temp = headB;
while (temp != null) {
if (visited.contains(temp)) {
return temp;
}
temp = temp.next;
}
return null;
}
}
遍历一个链表存入Set里,再遍历另一个查找有没有一样的
206. 反转链表
反转链表需要三个变量:
-
ListNode prev = null; // 1. 记录"前一个节点"
-
ListNode curr = head; // 2. 记录"当前节点"
-
ListNode nextTemp = null; // 3. 记录"下一个节点"
nextTemp = curr.next; // 第一步:先保存后继节点
curr.next = prev; // 第二步:安全地指针
prev = curr; // 第三步:prev 往前走一步
curr = nextTemp; // 第四步:curr 往前走一步
java
/**
* 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) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
234. 回文链表
java
/**
* 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) {
List<Integer> vals = new ArrayList<Integer>();
// 将链表的值复制到数组中
ListNode currentNode = head;
while (currentNode != null) {
vals.add(currentNode.val);
currentNode = currentNode.next;
}
// 使用双指针判断是否回文
int front = 0;
int back = vals.size() - 1;
while (front < back) {
if (!vals.get(front).equals(vals.get(back))) {
return false;
}
front++;
back--;
}
return true;
}
}
List 不能使用 [] 来获取或设置元素
==vsequals()
| 比较方式 | 含义 |
|---|---|
== |
比较两个引用是否指向同一个对象(即内存地址是否相同) |
.equals() |
比较两个对象的内容是否相等(可被重写) |
进阶
把空间复杂度降为O(1)

使用两个快慢指针找到前一半的结尾(找到链表的中点分割两部分):
- 快指针走2步
- 慢指针走1步
具体过程:
- 寻找链表的中心元素把链表一分为二
- 翻转后半段链表
- 对比两部分链表
java
/**
* 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) {
//寻找链表的中心元素把链表一分为二
ListNode slow = head;
ListNode fast = head.next;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
//翻转后半段链表 并把前半段和后半段分开
ListNode second = reverseList(slow.next);
slow.next = null;
//对比两部分链表
ListNode first = head;
while(first != null && second != null){
if(first.val != second.val)return false;
first = first.next;
second =second.next;
}
return true;
}
private ListNode reverseList(ListNode head){
if(head == null)return head;
ListNode curr = head;
ListNode pre = null;
while(curr != null){
ListNode tempNext = curr.next;
curr.next = pre;
pre = curr;
curr = tempNext;
}
return pre;
}
}
为啥快指针走2 慢指针走1?
因为快指针速度是慢指针的2倍,所以走过的路程也是慢指针的2倍,当快指针到达终点时,慢指针刚好在总长的一半
如果链表时是奇数,slow正好指到链表中点,分割出来的前半部分多一个节点,对判断有影响吗?没有影响,因为
while(first != null && second != null)所以多出来的那个不比较,second空了就结束了
141. 环形链表
java
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> seen = new HashSet<ListNode>();
while (head != null) {
if (!seen.add(head)) {
return true;
}
head = head.next;
}
return false;
}
}
seen.add(head):尝试把当前节点head添加到集合中。- 如果该节点之前没访问过 ,添加成功,返回
true。 - 如果该节点已经存在 (说明之前访问过),添加失败,返回
false。
- 如果该节点之前没访问过 ,添加成功,返回
优化
设置两个快慢指针,快指针套圈追赶慢指针,套到了就证明链表有回路
java
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) return true;
}
return false;
}
}
142. 环形链表 II
在添加进Set前加一步,如果Set包含该节点就说明该节点是入环的第一个节点
java
/**
* 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) {
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
}
优化

+c 只是 k=1k=1 时的特例。无论快指针套了多少圈,只要两个指针重新以相同的速度(1步/次)前进,它们一定会在环的入口相遇
java
/**
* 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) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
//先找两个指针相遇的地方
if(slow == fast){
//把快指针移回起点,速度一样,两指针相遇的地方就是环入口
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
}
为啥要判断
fast != null && fast.next != null为了保证
fast.next.next这行代码能安全地执行