引言
链表是最基础也是最重要的数据结构之一,在算法面试中频繁出现。本文将系统讲解链表的原理、实现和经典算法题,帮助你全面掌握链表。
一、链表基础
1.1 什么是链表?
链表是一种线性数据结构,通过指针将一系列节点连接起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; } }
ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3);
|
1.2 数组 vs 链表
| 特性 |
数组 |
链表 |
| 内存分配 |
连续 |
不连续 |
| 随机访问 |
O(1) |
O(n) |
| 插入/删除 |
O(n) |
O(1) |
| 空间开销 |
无额外开销 |
指针开销 |
| 缓存友好 |
是 |
否 |
二、单链表实现
2.1 基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| public class LinkedList { private ListNode head; private int size; public void addFirst(int val) { ListNode newNode = new ListNode(val); newNode.next = head; head = newNode; size++; } public void addLast(int val) { ListNode newNode = new ListNode(val); if (head == null) { head = newNode; } else { ListNode curr = head; while (curr.next != null) { curr = curr.next; } curr.next = newNode; } size++; } public void add(int index, int val) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException(); } if (index == 0) { addFirst(val); return; } ListNode newNode = new ListNode(val); ListNode prev = getNode(index - 1); newNode.next = prev.next; prev.next = newNode; size++; } public int removeFirst() { if (head == null) { throw new NoSuchElementException(); } int val = head.val; head = head.next; size--; return val; } public int removeLast() { if (head == null) { throw new NoSuchElementException(); } if (head.next == null) { int val = head.val; head = null; size--; return val; } ListNode prev = head; while (prev.next.next != null) { prev = prev.next; } int val = prev.next.val; prev.next = null; size--; return val; } public int remove(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException(); } if (index == 0) { return removeFirst(); } ListNode prev = getNode(index - 1); int val = prev.next.val; prev.next = prev.next.next; size--; return val; } private ListNode getNode(int index) { ListNode curr = head; for (int i = 0; i < index; i++) { curr = curr.next; } return curr; } public int get(int index) { return getNode(index).val; } public void set(int index, int val) { getNode(index).val = val; } public int indexOf(int val) { ListNode curr = head; for (int i = 0; i < size; i++) { if (curr.val == val) { return i; } curr = curr.next; } return -1; } public void print() { ListNode curr = head; while (curr != null) { System.out.print(curr.val + " -> "); curr = curr.next; } System.out.println("null"); } }
|
三、双向链表
3.1 双向链表节点
1 2 3 4 5 6 7 8 9
| public class DoubleListNode { int val; DoubleListNode prev; DoubleListNode next; DoubleListNode(int val) { this.val = val; } }
|
3.2 双向链表实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class DoublyLinkedList { private DoubleListNode head; private DoubleListNode tail; private int size; public void addFirst(int val) { DoubleListNode newNode = new DoubleListNode(val); if (head == null) { head = tail = newNode; } else { newNode.next = head; head.prev = newNode; head = newNode; } size++; } public void addLast(int val) { DoubleListNode newNode = new DoubleListNode(val); if (tail == null) { head = tail = newNode; } else { tail.next = newNode; newNode.prev = tail; tail = newNode; } size++; } public int removeFirst() { if (head == null) { throw new NoSuchElementException(); } int val = head.val; if (head == tail) { head = tail = null; } else { head = head.next; head.prev = null; } size--; return val; } public int removeLast() { if (tail == null) { throw new NoSuchElementException(); } int val = tail.val; if (head == tail) { head = tail = null; } else { tail = tail.prev; tail.next = null; } size--; return val; } }
|
四、经典链表算法
4.1 反转链表(LeetCode 206)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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; }
public ListNode reverseListRecursive(ListNode head) { if (head == null || head.next == null) { return head; } ListNode newHead = reverseListRecursive(head.next); head.next.next = head; head.next = null; return newHead; }
|
4.2 检测环(LeetCode 141)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public boolean hasCycle(ListNode head) { if (head == null || head.next == 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 || head.next == 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 ptr = head; while (ptr != slow) { ptr = ptr.next; slow = slow.next; } return ptr; } } return null; }
|
4.3 合并两个有序链表(LeetCode 21)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode curr = dummy; while (l1 != null && l2 != null) { if (l1.val < l2.val) { curr.next = l1; l1 = l1.next; } else { curr.next = l2; l2 = l2.next; } curr = curr.next; } curr.next = (l1 != null) ? l1 : l2; return dummy.next; }
|
4.4 删除倒数第N个节点(LeetCode 19)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode fast = dummy; ListNode slow = dummy; for (int i = 0; i <= n; i++) { fast = fast.next; } while (fast != null) { fast = fast.next; slow = slow.next; } slow.next = slow.next.next; return dummy.next; }
|
4.5 找到中间节点(LeetCode 876)
1 2 3 4 5 6 7 8 9 10 11
| public ListNode middleNode(ListNode head) { ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
|
4.6 判断回文链表(LeetCode 234)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public boolean isPalindrome(ListNode head) { if (head == null || head.next == null) { return true; } ListNode slow = head; ListNode fast = head; while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next; } ListNode secondHalf = reverseList(slow.next); ListNode p1 = head; ListNode p2 = secondHalf; boolean result = true; while (p2 != null) { if (p1.val != p2.val) { result = false; break; } p1 = p1.next; p2 = p2.next; } slow.next = reverseList(secondHalf); return result; }
|
4.7 相交链表(LeetCode 160)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) { 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; }
|
4.8 排序链表(LeetCode 148)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public ListNode sortList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode mid = getMid(head); ListNode right = mid.next; mid.next = null; ListNode left = sortList(head); right = sortList(right); return merge(left, right); }
private ListNode getMid(ListNode head) { ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
private ListNode merge(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode curr = dummy; while (l1 != null && l2 != null) { if (l1.val < l2.val) { curr.next = l1; l1 = l1.next; } else { curr.next = l2; l2 = l2.next; } curr = curr.next; } curr.next = (l1 != null) ? l1 : l2; return dummy.next; }
|
五、进阶技巧
5.1 虚拟头节点(Dummy Node)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public ListNode removeElements(ListNode head, int val) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode curr = dummy; while (curr.next != null) { if (curr.next.val == val) { curr.next = curr.next.next; } else { curr = curr.next; } } return dummy.next; }
|
5.2 快慢指针
5.3 递归
1 2 3 4 5 6 7 8 9 10 11 12
| public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode newHead = reverseList(head.next); head.next.next = head; head.next = null; return newHead; }
|
六、复杂度分析
| 操作 |
数组 |
单链表 |
双向链表 |
| 访问 |
O(1) |
O(n) |
O(n) |
| 头部插入 |
O(n) |
O(1) |
O(1) |
| 尾部插入 |
O(1) |
O(n) |
O(1) |
| 头部删除 |
O(n) |
O(1) |
O(1) |
| 尾部删除 |
O(1) |
O(n) |
O(1) |
| 中间插入 |
O(n) |
O(1)* |
O(1)* |
| 中间删除 |
O(n) |
O(1)* |
O(1)* |
*需要先找到位置,实际为O(n)
七、常见陷阱
7.1 空指针
1 2 3 4
| if (head == null || head.next == null) { return head; }
|
7.2 环形链表
1 2 3 4
| if (hasCycle(head)) { }
|
7.3 内存泄漏
1 2 3 4
| ListNode toDelete = curr.next; curr.next = toDelete.next; toDelete.next = null;
|
总结
链表核心要点:
- 基本操作:插入、删除、遍历
- 经典算法:反转、环检测、合并、排序
- 常用技巧:虚拟头、快慢指针、递归
- 注意事项:空指针、边界条件、环形链表
参考资料