目录
[6. 从尾到头打印链表](#6. 从尾到头打印链表)
[18.1 在 O(1) 时间内删除链表节点](#18.1 在 O(1) 时间内删除链表节点)
[18.2 删除链表中重复的结点](#18.2 删除链表中重复的结点)
[22. 链表中倒数第 K 个结点](#22. 链表中倒数第 K 个结点)
[23. 链表中环的入口结点](#23. 链表中环的入口结点)
[24. 反转链表](#24. 反转链表)
[25. 合并两个排序的链表](#25. 合并两个排序的链表)
[35. 复杂链表的复制](#35. 复杂链表的复制)
[52. 两个链表的第一个公共结点](#52. 两个链表的第一个公共结点)
6. 从尾到头打印链表
java
package linkedList;
import java.util.ArrayList;
import java.util.Stack;
// ====================== 核心思路 ======================
// 题目:从尾到头打印链表,用数组返回结果
// 解法:利用栈 "先进后出" 的特性实现逆序
// 1. 遍历链表,把所有节点值依次压入栈
// 2. 依次弹出栈中元素,存入 ArrayList
// 3. 最终得到的集合就是链表从尾到头的顺序
// 时间复杂度:O(n) 遍历两次链表
// 空间复杂度:O(n) 需要一个栈存储所有节点
// ======================================================
public class PrintLinkedListFromTailToHead {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<Integer> stack = new Stack<>();
while (listNode != null) {
stack.add(listNode.val);
listNode = listNode.next;
}
ArrayList<Integer> res = new ArrayList<>();
while (!stack.isEmpty()) {
res.add(stack.pop());
}
return res;
}
public static void main(String[] args) {
ListNode listNode = new ListNode(1);
listNode.next = new ListNode(2);
listNode.next.next = new ListNode(3);
PrintLinkedListFromTailToHead test = new PrintLinkedListFromTailToHead();
System.out.println(test.printListFromTailToHead(listNode));
}
}
18.1 在 O(1) 时间内删除链表节点
java
package linkedList;
// ====================== 核心思路 ======================
// 题目:O(1) 时间复杂度删除链表指定节点
// 解法:不遍历找前驱,直接复制后继节点覆盖当前节点
// 1. 若待删节点不是尾节点:用下一个节点的值和指针覆盖它,O(1) 完成删除
// 2. 若待删节点是尾节点:只能从头遍历找到前驱,断开连接
// 3. 若链表只有一个节点(头节点=待删节点):直接返回 null
// 时间复杂度:平均 O(1),最坏 O(n)(删除尾节点)
// 空间复杂度:O(1)
// ======================================================
public class DeleteNodeInLinkedList {
public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
// write code here
if (head == null || tobeDelete == null) {
return null;
}
if (tobeDelete.next == null) {
if (head == tobeDelete) {
return null;
} else {
ListNode cur = head;
while (cur.next != tobeDelete) cur = cur.next;
cur.next = null;
}
} else {
ListNode temp = tobeDelete.next;
tobeDelete.val = temp.val;
tobeDelete.next = temp.next;
}
return head;
}
}
18.2 删除链表中重复的结点
java
package linkedList;
// ====================== 核心思路 ======================
// 题目:删除排序链表中所有重复的节点(重复节点不保留)
// 解法:递归法,利用排序链表重复节点相邻的特性
// 1. 递归终止条件:节点为空 或 只有一个节点,直接返回
// 2. 如果当前节点与下一个节点值相等:跳过所有重复节点,递归处理后续节点
// 3. 如果不相等:保留当前节点,递归处理下一个节点并连接
// 4. 最终返回去重后的链表头节点
// 时间复杂度:O(n) 遍历一次链表
// 空间复杂度:O(n) 递归调用栈空间
// ======================================================
public class RemoveDuplicateNodesInSortedList {
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return pHead;
}
ListNode next = pHead.next;
if (pHead.val == next.val) {
while (next != null && pHead.val == next.val) {
next = next.next;
}
return deleteDuplication(next);
} else {
pHead.next = deleteDuplication(pHead.next);
return pHead;
}
}
public static void main(String[] args) {
ListNode listNode = new ListNode(1);
listNode.next = new ListNode(2);
listNode.next.next = new ListNode(3);
listNode.next.next.next = new ListNode(3);
listNode.next.next.next.next = new ListNode(4);
listNode.next.next.next.next.next = new ListNode(4);
listNode.next.next.next.next.next.next = new ListNode(5);
RemoveDuplicateNodesInSortedList test = new RemoveDuplicateNodesInSortedList();
ListNode res = test.deleteDuplication(listNode);
while (res != null) {
System.out.println(res.val);
res = res.next;
}
}
}
22. 链表中倒数第 K 个结点
java
package linkedList;
// ====================== 核心思路 ======================
// 题目:输出链表的倒数第k个节点
// 解法:快慢指针(双指针),一次遍历搞定,空间 O(1)
// 1. 快指针先走 k-1 步,指向第k个节点
// 2. 若快指针无法走完k-1步,说明链表长度不足k,返回null
// 3. 然后快慢指针同时向后走,直到快指针到达链表尾部
// 4. 此时慢指针指向的就是倒数第k个节点
// 时间复杂度:O(n) 一次遍历
// 空间复杂度:O(1) 仅用两个指针
// ======================================================
public class FindKthToTailInLinkedList {
public ListNode FindKthToTail(ListNode pHead, int k) {
// write code here
if (pHead == null || k == 0) return null;
ListNode pTail = pHead;
while (pTail.next != null && k > 1) {
pTail = pTail.next;
k--;
}
if (k > 1) return null;
while (pTail.next != null) {
pHead = pHead.next;
pTail = pTail.next;
}
return pHead;
}
}
23. 链表中环的入口结点
java
package linkedList;
// ====================== 核心思路 ======================
// 题目:找到链表中环的入口节点,无环则返回null
// 解法:快慢指针法(龟兔赛跑)
// 1. 快指针走2步,慢指针走1步,相遇则说明有环
// 2. 快指针回到链表头部,快慢指针都走1步
// 3. 再次相遇的位置,就是环的入口
// 时间复杂度 O(n):
// 快慢指针最多遍历整个链表两次(找相遇点+找入口点),常数次遍历仍为线性复杂度
// 空间复杂度 O(1):
// 仅使用 fast、slow 两个指针,无额外辅助空间,是原地算法
// ======================================================
public class EntryNodeOfLinkedListLoop {
public ListNode EntryNodeOfLoop(ListNode pHead) {
//快慢指针法(龟兔赛跑法)使用双指针,一个快指针 fast 每次移动两个节点,一个慢指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。
if (pHead == null) return null;
ListNode pFast = pHead, pSlow = pHead;
// 第一步:判断是否有环
while (pFast != null && pFast.next != null) {
pFast = pFast.next.next;
pSlow = pSlow.next;
if (pFast == pSlow) {
break;
}
}
// 无环直接返回
if(pFast == null || pFast.next == null){
return null;
}
// 第二步:找环入口
pFast = pHead;
while (pFast != pSlow) {
pFast = pFast.next;
pSlow = pSlow.next;
}
return pFast;
}
}
24. 反转链表
java
package linkedList;
import java.util.Stack;
// ====================== 核心思路 ======================
// 题目:反转单链表,返回反转后的新头节点
// 解法:迭代法(三指针反转),原地修改链表指向,不占用额外空间
// 1. 定义三个指针 pre、cur、nxt 分别表示前驱、当前、后继节点
// 2. 遍历链表,逐个将当前节点的 next 指向前驱节点
// 3. 不断更新指针位置,直到遍历结束
// 4. 最终 pre 指向新的头节点
// 时间复杂度:O(n) 遍历一次链表
// 空间复杂度:O(1) 只用了常数个指针变量
// ======================================================
public class ReverseList {
public ListNode ReverseList(ListNode head) {
// write code here
if (head == null || head.next == null) return head;
ListNode cur = head;
ListNode pre = null;
ListNode nxt;
while (cur != null) {
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
public static void main(String[] args) {
ReverseList test = new ReverseList();
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
ListNode listNode = test.ReverseList(head);
while (listNode != null) {
System.out.println(listNode.val);
listNode = listNode.next;
}
}
}
25. 合并两个排序的链表
java
package linkedList;
public class MergeTwoSortedLists {
// ====================== 核心思路 ======================
// 题目:合并两个递增排序的链表,合并后依然有序
// 解法:双指针迭代法,原地合并,不占用额外空间
// 1. 创建虚拟头节点,方便统一处理链表头部
// 2. 用双指针同时遍历两个有序链表
// 3. 每次取较小值的节点接入结果链表
// 4. 一个链表遍历完后,直接拼接另一个链表剩余部分
// 时间复杂度:O(n) 遍历一次两个链表
// 空间复杂度:O(1) 仅使用常数个指针变量
// ======================================================
public ListNode Merge(ListNode pHead1, ListNode pHead2) {
// write code here
ListNode res = new ListNode(-1);
ListNode cur = res;
while (pHead1 != null && pHead2 != null) {
if (pHead1.val < pHead2.val) {
cur.next = pHead1;
pHead1 = pHead1.next;
} else {
cur.next = pHead2;
pHead2 = pHead2.next;
}
cur = cur.next;
}
if (pHead1 != null) {
cur.next = pHead1;
} else {
cur.next = pHead2;
}
return res.next;
}
// ====================== 核心思路 ======================
// 题目:合并两个递增排序的链表,合并后依然有序
// 解法:递归法(简洁优雅)
// 1. 递归终止条件:一个链表为空,直接返回另一个链表
// 2. 比较两个链表头节点值大小
// 3. 值小的节点作为当前头,它的 next 指向剩余部分递归合并的结果
// 4. 层层返回,自动拼接成完整有序链表
// 时间复杂度:O(n) 每个节点递归处理一次
// 空间复杂度:O(n) 递归调用栈占用空间
// ======================================================
public ListNode Merge1(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null) return pHead2;
if (pHead2 == null) return pHead1;
if (pHead1.val < pHead2.val) {
pHead1.next = Merge1(pHead1.next, pHead2);
return pHead1;
} else {
pHead2.next = Merge1(pHead1, pHead2.next);
return pHead2;
}
}
public static void main(String[] args) {
MergeTwoSortedLists test = new MergeTwoSortedLists();
ListNode pHead1 = new ListNode(-1);
pHead1.next = new ListNode(2);
pHead1.next.next = new ListNode(4);
ListNode pHead2 = new ListNode(1);
pHead2.next = new ListNode(3);
pHead2.next.next = new ListNode(4);
ListNode listNode = test.Merge1(pHead1, pHead2);
while (listNode != null) {
System.out.println(listNode.val);
listNode = listNode.next;
}
}
}
35. 复杂链表的复制
java
package linkedList;
// ====================== 核心思路 ======================
// 题目:复制复杂链表(包含 next 和 random 指针)
// 解法:原地三步法(空间 O(1))
// 1. 在每个原节点后面插入一个克隆节点
// 2. 根据原节点的 random 给克隆节点赋值 random
// 3. 拆分原链表与克隆链表,得到深拷贝结果
// 时间复杂度:O(n)
// 空间复杂度:O(1)
// ======================================================
public class CopyRandomList {
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null) return null;
RandomListNode cur = pHead;
// 每个节点后面接一个复制的节点
while (cur != null) {
RandomListNode curCopy = new RandomListNode(cur.label);
curCopy.next = cur.next;
cur.next = curCopy;
cur = curCopy.next;
}
// 给复制的节点赋random指针
cur = pHead;
while (cur != null) {
RandomListNode curCopy = cur.next;
if (cur.random != null) curCopy.random = cur.random.next;
cur = curCopy.next;
}
// 剪出curCopy
RandomListNode res = pHead.next;
cur = pHead;
while (cur.next != null) {
RandomListNode next = cur.next;
cur.next = next.next;
cur = next;
}
return res;
}
}
52. 两个链表的第一个公共结点
java
package linkedList;
// ====================== 核心思路 ======================
// 题目:寻找两个无环单向链表的第一个公共节点
// 解法:双指针浪漫相遇法(最优解)
// 1. 定义两个指针 cur1、cur2 分别从两个链表头出发
// 2. 一个指针走到末尾后,指向另一个链表的头部继续走
// 3. 最终两个指针会在第一个公共节点相遇(或同时为null)
// 原理:路程相等,速度相同,必然相遇
// 时间复杂度:O(n) 两个指针各遍历两次链表
// 空间复杂度:O(1) 仅使用两个指针,无额外空间
// ======================================================
public class FindFirstCommonNode {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode cur1 = pHead1, cur2 = pHead2;
while (cur1 != cur2) {
cur1 = (cur1 == null) ? pHead2 : cur1.next;
cur2 = (cur2 == null) ? pHead1 : cur2.next;
// if (cur1 != null) {
// cur1 = cur1.next;
// } else { // 走到null切换走另一个
// cur1 = pHead2;
// }
// if (cur2 != null) {
// cur2 = cur2.next;
// } else {
// cur2 = pHead1;
// }
}
return cur1;
}
}