两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 比如:1→2→3→4 交换完成后为 2→1→4→3
没啥可说的,画个图应该就搞出来了:
⨳ 找到两个交换节点(node_1
,node_2
)的前一个节点(prev_node
);
⨳ prev_node
的后继指针指向 node_2
(相当于把 node_1
从链表中删除节点)
⨳ node_1
的后继指针指向 node_2
的后继节点,node_2
的后继指针指向 node_1
(相当于从链表中插入 node_1
)
为了统一对头节点的操作,设置虚拟节点是很好的选择:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dumy_head = new ListNode(0,head);// 为链表设置虚拟头节点
ListNode pre_node = dumy_head;
while(pre_node.next !=null && pre_node.next.next !=null ){
ListNode node1 = pre_node.next;
ListNode node2 = pre_node.next.next;
// 将 node1 从链表中删除
pre_node.next = node2;
// 将 node1 重新插入链表中
node1.next = node2.next;
node2.next = node1;
// 计算下一个 pre_node
pre_node = pre_node.next.next;
}
// 用完dumy_head,别忘了将其从链表中拿出去
ListNode new_head = dumy_head.next;
dumy_head.next = null;
return new_head;
}
}
删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。
上文讲链表基础时,我们是从头到尾遍历链表,删除的是第n
个节点,现在要删除倒数第 n
个节点,题中的链表又不是双向链表,链表的长度也没有给出。
难道需要从头到尾遍历两次,第一次遍历记录倒数第 n
个节点是正数第几个节点,第二次遍历在进行删除?
两次遍历可以是可以,但如果用到数组篇讲的快慢指针,一次遍历也是可以的。
快指针一般用于检索,那就让快指针比慢指针提前走 n
步,当快指针走到尾节点时,慢指针指向的节点就是要删除节点的前一个节点。
为了统一对头节点的操作,还是设置虚拟节点进行操作:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dumy_head = new ListNode(0,head);
ListNode fast_index = dumy_head;
ListNode slow_index = dumy_head;
// 将快指针领先 n 步
for(int i=0;i<n;i++){
fast_index = fast_index.next;
}
// 快慢指针一起出发
while(fast_index.next!=null){
slow_index = slow_index.next;
fast_index = fast_index.next;
}
// 这时 slow_index 指向要删除节点的前一个节点
ListNode rm_node = slow_index.next;
slow_index.next = rm_node.next;
rm_node.next = null;
// 删除 dumy_head
ListNode new_node = dumy_head.next;
dumy_head.next = null;
return new_node;
}
}
反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。比如 1->2->3->4->5->NULL 反转后 5->4->3->2->1->NULL'
反转链表也可以使用快慢指针进行处理,将链表看作两部分,慢指针左半部分为新链表,慢指针右半部分为需要处理的节点:
⨳ 快指针用于检索,快指针指向的节点即为要插入新链表头部位置的节点
⨳ 慢指针用于记录,即慢指针指向新链表的头节点
既然对链表的操作是在头部插入节点,就不需要使用虚拟节点统一逻辑了:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null){
return head;
}
ListNode slow_index = null;
ListNode fast_index = head;
while(fast_index!=null){
// 记录 fast_index 下一个节点
ListNode fast_index_next = fast_index.next;
// 将 fast_index 指向的节点,插入新链表头部
fast_index.next = slow_index;
// 指针移动
slow_index = fast_index;
fast_index = fast_index_next;
}
return slow_index;
}
}
链表相交
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。题目数据 保证 整个链式结构中不存在环。
注意 ,函数返回结果后,链表必须 保持其原始结构 。
要注意,交点不是数值相等,而是指针相等,比如:
两个链表如果相交,那相交节点及其后边的节点一定是一样的。
那是不是可以使用双指针,让两个指针分别指向两个链表的尾节点,然后两个指针一起从后向前遍历,比较指针是否相同,最后一个相同的节点便是交点。
这样做是可以,但题中的链表是单向链表,从尾向头遍历不现实,那怎么办,先将链表反转吗?
题中要求链表必须 保持其原始结构 ,反转也不能用,怎么办?
我们真正的目的是让两个指针同步,这样才能比较交点,既然尾部对齐可以比较交点,那反过来,让两个链表长度一致,这样从头到尾遍历,也就相当于从尾向头遍历了,只不过一个是找第一个相同的节点,一个是找最后一个相同的节点。
让两个链表长度一致,不需要截取链表,仅仅让较长的链表先跑一下就可以了:
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a_index = headA; // 指向A链表头节点的指针
ListNode b_index = headB; // 指向B链表头节点的指针
// 比较两个链表的长度,看看哪个指针先跑
int a_size = 0;
ListNode a_node= headA;
while(a_node!=null){
a_size++;
a_node =a_node.next;
}
int b_size = 0;
ListNode b_node = headB;
while(b_node!=null){
b_size++;
b_node =b_node.next;
}
int gap = a_size-b_size; // 计算先跑多少步
// A 链表比较长,那 a指针先跑
while(gap>0){
a_index = a_index.next;
gap--;
}
// B 链表比较长,那 b指针先跑
while(gap<0){
b_index = b_index.next;
gap++;
}
// 二者同时跑
while(a_index !=null && b_index !=null ){
if(a_index==b_index){
return a_index;
}
a_index=a_index.next;
b_index=b_index.next;
}
return null;
}
}
环形链表
给你一个链表的头节点
head
,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数
pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回
true
。 否则,返回false
。
这道题就是判断链表的一部分是不是可以构成循环链表。咋判断呢?
还是快慢指针的思想,让快指针跑的快(比如一次遍历两个节点),慢指针跑的慢(比如一次遍历一个节点)。
快慢指针一旦进入环形链表,就会一直在环中循环,而且快指针肯定会比慢指针先进入环,等到慢指针进入环,二者会一起在环中循环,又因为快指针跑的快,所以它必会追上慢指针。
这里的追上,是指快指针和慢指针相遇,也就是快指针指向的节点等于慢指针指向的节点,那为啥一定能相遇。
这是因为快指针是走两步,慢指针是走一步,其实相对于慢指针来说,快指针是一个节点一个节点的靠近慢指针的,所以快指针一定可以和慢指针相遇。
java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast_index = head;
ListNode slow_index = head;
while(fast_index!=null && fast_index.next!=null){
fast_index = fast_index.next.next;
slow_index = slow_index.next;
if(fast_index==slow_index)
return true;
}
return false;
}
}
环形链表II
给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始 )。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
这道题除了判断链表的一部分是不是可以构成循环链表,还要定位进入循环链表的第一个节点。
要解这道题就要列式子算术了:
假设从头节点到环形入口节点的节点数为x
。 环形入口节点到快指针与慢指针相遇节点的节点数为y
。 从相遇节点再到环形入口节点的节点数为 z
。
那么相遇时: 慢指针走过的节点数为: x + y
, 快指针走过的节点数:x + y + n (y + z)
,n
为快指针在环内走了n
圈才遇到慢指针, (y+z)
为 一圈内节点的个数。
因为快指针是一步走两个节点,慢指针一步走一个节点, 所以快指针走过的节点数 = 慢指针走过的节点数 * 2: (x + y) * 2 = x + y + n (y + z)
,即 (x + y) = n (y + z)
,此为一个等式。
因为求的是从头节点到环形入口节点的节点数 x
,将 x
移到等式的左边,则 x = n (y + z) - y
,再从 n(y+z)
中提出一个 (y+z)
来,则 x = (n - 1) (y + z) + z
。
现在仔细分析这个式子:x = (n - 1) (y + z) + z
⨳ n
一定是大于等于 1
的,因为快指针至少要多走一圈才能相遇慢指针。
⨳ (y + z)
是环形链表的节点数
⨳ z
是相遇节点到环形入口节点的节点数
当 n
等于 1
时,表示快指针在环内走了一圈就遇到了慢指针,化简式子为 x=z
,也就是从头节点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
当 n
大于 1
时,表示快指针在环内走了n
圈才遇到慢指针,一样可以从头节点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast_index = head;
ListNode slow_index = head;
while(fast_index!=null && fast_index.next !=null){
fast_index = fast_index.next.next;
slow_index = slow_index.next;
if(fast_index == slow_index ){
ListNode meet_index = fast_index;// 相遇点
ListNode new_index = head; // 重新从 head 节点出发的指针
while(meet_index!=new_index){
meet_index = meet_index.next;
new_index = new_index.next;
}
return new_index;
}
}
return null;
}
}