看这篇文章之前,可以先看看链表操作I。这里再次分享两个链表题,这两个链表题有都一个共同的特点,就是在头结点之前添加一个虚拟头结点,也称为哑结点(dummyHead)。先给出链表结构的定义。
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;
}
}
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
如1->4->5->null,2->3->7->8->12->null两个升序链表合并结果为1->2->3->4->5->7->8->12->null。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummyHead = new ListNode(-1);
ListNode node = dummyHead;
while(list1 != null && list2 != null){
if(list1.val < list2.val){
node.next = list1;
list1 = list1.next;
}else{
node.next = list2;
list2 = list2.next;
}
node = node.next;
}
if(list1 != null){
node.next = list1;
}
if(list2 != null){
node.next = list2;
}
return dummyHead.next;
}
}
以(list1)1->4->8->null和(list2)2->5->6->12->14->null为例,说明合并两个升序链表的过程。首先定义一个虚拟头结点-1->null。之后定义一个node指针指向虚拟头结点。进入while循环。进入if-else判断,比较list1和list2首个元素的大小,1<2,所以node.next = list1,也就是-1->1->4->8->null。之后将list1指向下一个结点4,node指向1这个结点。
比较4和2大小,4>2,则node.next = list2,也就是-1->1->2->5->6->12->14->null。之后将list2指向下一个结点5,node指向2这个结点。
比较4和5大小,4 < 5,node.next = list1,也就是-1->1->2->4->8->null。list1指向下一个结点8,node指向4这个结点。
比较8和5的大小,8 > 5,node.next = list2,也就是-1->1->2->4->5->6->12->14->null。list2移动到下一个结点6,node指向5这个结点。
比较8和6的大小,8 > 6,则node.next = list2,也就是-1->1->2->4->5->6->12->14->null。list2移动到下一个结点12,node指向6这个结点。
比较8和12的大小,8 < 12,则node.next = list1,也就是-1->1->2->4->5->6->8->null。list1移动到下一个结点null,node指向8这个结点。进入下一轮循环因list == null不满足条件跳出循环。
由于list1==null,不满足if(list != null)这个if条件。由于list2指向12,不等于null,满足if(list2 != null)这个条件,将node->next = list2,也就是-1->1->2->4->5->6->8->12->14->null。也就是将list2中剩余没有遍历完的升序链表段拼接在结果链表的末尾。虚拟头结点-1的next即为最终的合并结果。
删除链表的倒数第N个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
如链表1->2->3->4->5->null,删除倒数第2个结点,结果为1->2->3->5->null。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode fast = dummyHead;
ListNode slow = dummyHead;
while(n >= 0){
fast = fast.next;
n--;
}
while(fast != null){
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return dummyHead.next;
}
}
这题的思路就是先遍历到倒数第N-1个结点,之后将第N-1个结点的next指向第N个结点的next,也就是第N-1个结点的next的next。现在的问题是如何通过一次遍历就找到链表的第N-1个结点。通过快慢指针,先让快指针先走N+1步,之后快慢指针同时走,当快指针走到链表末尾(null)时,slow就指向了链表的第N-1个结点。以1->2->3->4->null为例子,要删除3这个结点,也就是倒数第2个结点,定义fast,先走3步,fast指向了4这个结点,之后定义slow指针指向头结点,之后slow和fast同时走,slow走到2,fast走到null。以1->2->3->4->5->6->null为例子,要删除5这个结点,倒数第2个结点,fast先走3步,fast指向4,之后slow从1开始,fast从4开始,slow指向2,fast指向5,slow指向3,fast指向6,slow指向4,fast指向null。删除倒数第N个结点,fast先走N + 1步,和链表的长度没有关系。所以就算在链表头添加一个虚拟头结点,该规则也同样适用。但是添加虚拟头结点之后,一些特殊情况就可以采用相同的处理规则。
这里为什么需要定义一个虚拟头结点呢?如果要删除的链表的头结点,就需要对这个特殊情况进行特殊处理。比如1->2->null,删除倒数第二个结点,fast要走3步,显然fast只能走2步,无法走第三步。就会报错,因此删除头结点的这种情况需要特殊处理。
如果在原有链表上添加一个虚拟头结点,就无需特殊处理。设链表为1->null,添加一个虚拟头结点-1->1->null。此时删除1这个结点,也就是倒数第1个,fast走两步,fast指向null,slow指向虚拟头结点-1,因为fast为null,没有了slow和fast同时移动的过程。slow.next = slow.next.next,也就是-1->null,把1直接删除。