LeetCode - 合并两个有序链表 / 删除链表的倒数第 N 个结点
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
-
两个链表的节点数目范围是
[0, 50]
-
-100 <= Node.val <= 100
-
l1
和l2
均按 非递减顺序 排列
解题思路
这段代码实现了合并两个有序链表的功能,采用递归方式实现。
-
创建虚拟头节点:
ListNode head = new ListNode(-1); // 创建一个值为-1的虚拟头节点
-
递归合并过程:
-
基线条件:当两个链表都为空时,递归结束
if (list1 == null && list2 == null) {return head; }
-
比较并连接节点:
-
当两个链表都不为空时,比较节点值,取较小者
if (list1.val > list2.val) {head.next = new ListNode(list2.val); // 创建新节点list2 = list2.next; // 移动指针 } else {head.next = new ListNode(list1.val);list1 = list1.next; }
-
当一个链表为空时,直接连接另一个链表的节点
else if (list1 == null) {head.next = new ListNode(list2.val);list2 = list2.next; } else if (list2 == null) {head.next = new ListNode(list1.val);list1 = list1.next; }
-
-
-
递归调用:
return doMergeTwoLists(head.next, list1, list2); // 处理下一个节点
-
返回结果:
return head.next; // 跳过虚拟头节点,返回真正的头节点
class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode head = new ListNode(-1);doMergeTwoLists( head, list1, list2);return head.next;}public ListNode doMergeTwoLists(ListNode head, ListNode list1, ListNode list2) {if (list1 == null && list2 == null) {return head;}if (list1 != null && list2 != null) {if (list1.val > list2.val) {head.next = new ListNode(list2.val);list2 = list2.next;} else {head.next = new ListNode(list1.val);list1 = list1.next;}}else if (list1 == null) {head.next = new ListNode(list2.val);list2 = list2.next;}else if (list2 == null) {head.next = new ListNode(list1.val);list1 = list1.next;}return doMergeTwoLists( head.next, list1, list2);}
}
删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
-
链表中结点的数目为
sz
-
1 <= sz <= 30
-
0 <= Node.val <= 100
-
1 <= n <= sz
栈(Stack)数据结构
解题思路
使用栈(Stack)数据结构来解决"删除链表倒数第N个节点"的问题。栈是一种后进先出(LIFO)的数据结构,可以用来反转链表的顺序,通过将链表节点全部压入栈中,然后弹出N个节点,找到要删除的节点及其前驱节点
-
第一次遍历:将链表所有节点依次压入栈
-
例如链表
1->2->3->4->5
,n=2 -
栈中内容(从底到顶):[1, 2, 3, 4, 5]
-
-
弹出阶段:
-
第一次弹出:5 (n=2 → n=1)
-
第二次弹出:4 (n=1 → n=0) → 找到要删除的节点4
-
查看栈是否为空:栈顶现在是3
-
弹出3作为前驱节点,将3的next指向4的next(即5)
-
结果链表:1->2->3->5
-
-
特殊情况处理:
-
如果要删除的是头节点(如n=链表长度),栈弹出后为空
-
直接返回头节点的next作为新头节点
-
public ListNode removeNthFromEnd(ListNode head, int n) {// 1. 创建栈并遍历链表,将所有节点压入栈中Stack<ListNode> stack = new Stack();ListNode node = head;while (node != null) {stack.push(node);node = node.next;}// 2. 弹出栈顶元素,直到找到倒数第N个节点while (n > 0) {ListNode temp = stack.pop();if (--n == 0) { // 当n减到0时,temp就是我们要删除的节点ListNode next = temp.next;// 3. 处理删除操作if (stack.isEmpty()) {// 如果栈为空,说明要删除的是头节点return next;} else {// 否则获取前驱节点并修改其next指针ListNode pre = stack.pop();pre.next = next;}break;}}return head;
}
快慢指针
解题思路
这个解法使用了快慢双指针技巧来高效地删除链表倒数第N个节点,无需使用额外空间(栈解法需要O(n)空间)。核心思想是:
-
快指针先走N+1步:这样快慢指针之间保持N个节点的间隔
-
同步移动双指针:当快指针到达链表末尾时,慢指针正好指向要删除节点的前驱节点
-
删除目标节点:通过修改慢指针的next指针实现删除
public ListNode removeNthFromEnd(ListNode head, int n) {// 创建虚拟头节点(dummy)ListNode dummy = new ListNode(0);dummy.next = head;// 初始化快慢指针ListNode fast = dummy;ListNode slow = dummy;// 快指针先走n+1步while (n >= 0) {fast = fast.next;n--;}// 同步移动双指针直到快指针到达末尾while (fast != null) {fast = fast.next;slow = slow.next;}// 删除目标节点slow.next = slow.next.next;// 返回真正的头节点(可能已被删除)return dummy.next;
}