一、理论基础
- 类型
- 单链表:链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null
- 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。可以向前查询也可以向后查询。
- 循环链表:链表首尾相连。
- 存储方式
-
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
-
- 定义
public class ListNode { // 结点的值 int val; // 下一个结点 ListNode next; // 节点的构造函数(无参) public ListNode() {} // 节点的构造函数(有一个参数) public ListNode(int val) { this.val = val; } // 节点的构造函数(有两个参数) public ListNode(int val, ListNode next) { this.val = val; this.next = next; } }
- 复杂度
插入/删除 查询 适用场景 数组 O(n) O(1) 数据量固定,频繁查询,较少插入 链表 O(1) O(n) 数据量不固定,频繁增删,较少查询
二、移除链表
-
题目
- 给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。
- 给你一个链表的头节点
-
思路:
- 头节点需要单独判断的,一般增加虚拟头节点
-
代码
class Solution { public ListNode removeElements(ListNode head, int val) { ListNode root = new ListNode(-1, head); ListNode curr = root; while (curr.next != null) { if (curr.next.val == val) { curr.next = curr.next.next; } else { curr = curr.next; } } return root.next; } }
三、设计链表
-
题目
-
思路
-
代码
class MyLinkedList { class ListNode{ ListNode next; int val; ListNode(){} ListNode(ListNode next, int val){ this.next = next; this.val = val; } } private ListNode head; private int size; public MyLinkedList() { head = new ListNode(); this.size = 0; } public int get(int index) { if (index < 0 || index >= size) { return -1; } ListNode curr = head; while (index-- > 0) { curr = curr.next; } return curr.next.val; } public void addAtHead(int val) { addAtIndex(0, val); } public void addAtTail(int val) { addAtIndex(size, val); } public void addAtIndex(int index, int val) { if (index < 0 || index > size) { return; } ListNode curr = head; while (index-- > 0) { curr = curr.next; } curr.next = new ListNode(curr.next, val); size++; } public void deleteAtIndex(int index) { if (index < 0 || index >= size) { return; } ListNode curr = head; while (index-- > 0) { curr = curr.next; } curr.next = curr.next.next; size--; } }
四、翻转链表
-
题目
- 给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
- 给你单链表的头节点
-
思路
-
代码
class Solution { public ListNode reverseList(ListNode head) { ListNode pre = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; curr.next = pre; pre = curr; curr = next; } return pre; } }
五、两两交换链表中的节点
-
题目
- 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点
-
思路
-
代码
class Solution { public ListNode swapPairs(ListNode head) { ListNode root = new ListNode(-1, head); ListNode curr = root; while (curr.next != null && curr.next.next != null) { ListNode next1 = curr.next; ListNode next2 = curr.next.next; ListNode next3 = curr.next.next.next; curr.next = next2; next2.next = next1; next1.next = next3; curr = next1; } return root.next; } }
六、删除链表的倒数第N个节点
-
题目
- 给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。
- 给你一个链表,删除链表的倒数第
-
思路
- 先让快指针走n步
- 慢指针继续走
- 头结点需要特殊处理的采用虚拟头结点
-
代码
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode root = new ListNode(-1, head); ListNode fast = root; ListNode slow = root; while (fast.next != null) { if (n > 0) { n--; } else { slow = slow.next; } fast = fast.next; } slow.next = slow.next.next; return root.next; } }
七、链表相交
-
题目
- 给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null
。
- 给你两个单链表的头节点
-
思路
-
代码
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode a = headA; ListNode b = headB; while (a != b) { a = a == null ? headB : a.next; b = b == null ? headA : b.next; } return a; } }
八、环形链表II
-
题目
- 给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。
- 给定一个链表的头节点
-
思路
-
代码
public class Solution { public ListNode detectCycle(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; // 存在环 if (fast == slow) { fast = head; while (fast != slow) { fast = fast.next; slow = slow.next; } return slow; } } return null; } }
九、总结
- 虚拟头节点:每次对应头结点的情况都要单独处理,可使用虚拟头结点的技巧