链表操作全攻略:从基础到进阶的算法技巧 🌈
链表是算法学习中的核心数据结构,掌握链表操作是每个程序员的必修课。本文通过生动讲解和完整代码示例,带你深入理解链表的各种操作技巧!
🌈 链表结构:程序世界的珍珠项链
链表就像一串珍珠项链,每个珍珠(节点)都包含两部分:
- 数据域:存储珍珠的价值(数据)
- 指针域:连接下一颗珍珠的链条(指针)
// Java中的链表节点定义
class ListNode {
int val; // 节点存储的值
ListNode next; // 指向下一个节点的指针
ListNode(int val) {
this.val = val;
this.next = null;
}
}
🗑️ 删除链表元素:精准"拆链"技巧
头节点删除:处理"换头"情况
public ListNode removeHeadElements(ListNode head, int target) {
// 删除所有值为target的头部节点
while (head != null && head.val == target) {
head = head.next;
}
return head;
}
普通节点删除:遍历寻找目标
public ListNode removeElements(ListNode head, int target) {
if (head == null) return null;
ListNode cur = head;
while (cur != null && cur.next != null) {
if (cur.next.val == target) {
// 跳过目标节点,直接连接下一个节点
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
🌟 虚拟头节点技巧:统一处理逻辑(推荐)
public ListNode removeElementsWithDummy(ListNode head, int target) {
// 创建虚拟头节点,指向真实头节点
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null) {
if (cur.next.val == target) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
// 返回虚拟头节点的下一个节点
return dummyHead.next;
}
虚拟头节点的优势:统一处理头节点和普通节点,避免特殊逻辑判断!
🛠️ LeetCode 707:设计链表实战
class MyLinkedList {
int size; // 链表长度
ListNode dummyHead; // 虚拟头节点
public MyLinkedList() {
size = 0;
dummyHead = new ListNode(0);
}
// 获取第index个节点的值
public int get(int index) {
if (index < 0 || index >= size) return -1;
ListNode cur = dummyHead.next;
while (index-- > 0) {
cur = cur.next;
}
return cur.val;
}
// 头部插入节点
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = dummyHead.next;
dummyHead.next = newNode;
size++;
}
// 尾部插入节点
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode cur = dummyHead;
while (cur.next != null) {
cur = cur.next;
}
cur.next = newNode;
size++;
}
// 在第index个节点前插入
public void addAtIndex(int index, int val) {
if (index > size) return;
if (index < 0) index = 0;
ListNode cur = dummyHead;
while (index-- > 0) {
cur = cur.next;
}
ListNode newNode = new ListNode(val);
newNode.next = cur.next;
cur.next = newNode;
size++;
}
// 删除第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
ListNode cur = dummyHead;
while (index-- > 0) {
cur = cur.next;
}
cur.next = cur.next.next;
size--;
}
}
🔄 翻转链表:经典双指针技巧
双指针法:直观易懂
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode temp = cur.next; // 临时保存下一个节点
cur.next = pre; // 翻转指针方向
pre = cur; // 移动pre指针
cur = temp; // 移动cur指针
}
return pre; // 新头节点
}
递归法:简洁优雅
public ListNode reverseListRecursive(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode pre, ListNode cur) {
if (cur == null) return pre;
ListNode temp = cur.next;
cur.next = pre;
return reverse(cur, temp);
}
🔀 两两交换链表节点:指针操作艺术
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null && cur.next.next != null) {
ListNode temp = cur.next; // 保存节点1
ListNode temp1 = cur.next.next.next; // 保存节点3
// 执行交换
cur.next = cur.next.next; // 虚拟头指向节点2
cur.next.next = temp; // 节点2指向节点1
temp.next = temp1; // 节点1指向节点3
cur = cur.next.next; // 移动指针
}
return dummyHead.next;
}
🎯 删除倒数第N个节点:快慢指针妙用
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode fast = dummyHead;
ListNode slow = dummyHead;
// 快指针先走n+1步
n++;
while (n-- > 0 && fast != null) {
fast = fast.next;
}
// 同时移动快慢指针
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 删除目标节点
slow.next = slow.next.next;
return dummyHead.next;
}
🔁 环形链表:龟兔赛跑算法
判断链表是否有环
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;
}
找到环形链表的入口
public ListNode detectCycle(ListNode head) {
if (head == null) return null;
ListNode slow = head;
ListNode fast = head;
boolean hasCycle = false;
// 第一阶段:判断是否有环
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
hasCycle = true;
break;
}
}
// 第二阶段:找到环入口
if (hasCycle) {
ListNode ptr = head;
while (ptr != slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
return null;
}
数学原理:当快慢指针相遇时,从头节点和相遇点同时出发的两个指针,将在环入口相遇!
💎 总结:链表操作的核心技巧
- 虚拟头节点是处理链表问题的万能钥匙
- 双指针技巧(快慢指针)解决90%的链表问题
- 递归思想让复杂操作变得简洁优雅
- 画图分析是理解链表操作的最佳方式
掌握这些链表操作技巧,你就能轻松应对大多数链表相关的算法面试题!🌈
欢迎在评论区分享你的链表解题心得,一起交流进步!👇