代码随想录第4天||删除节点||交换节点||环形链表||链表相交
📌心情总结:今天比昨天难多了啊啊啊啊
题目传送门:
19.删除链表的倒数第N个节点;24.两两交换链表中的节点;142.环形链表II;160.链表相交
一、删除链表的倒数第N个节点
1.题目简介
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
2.解题思路
一入眼想到的就是,如果需要一趟扫描实现,那就构造2个指针,一个使劲往后面走,另一个与它刚好相隔n个位置,当前面节点走到尽头的时候,后面节点就是需要删除的位置。
具体做法:生成两个结点a,b(位置在头节点)再生成一个节点连接到头节点prv_b(用于记录待删除结点的前一结点),首先让a先走n步,,在此之后a,b一起走,并且时刻记录b的前一结点位置到prv_b,当a到达null后,删除b即可。
3.题解代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode a = head;
ListNode b = head;
ListNode prv_b = new ListNode();
prv_b.next = head;
int cnt = 0;
if(head==null)return head;
if(head.next==null & n==1)return head.next;
for(;cnt<n;cnt++)a=a.next;
while(a!=null){
a = a.next;
prv_b = b;
b = b.next;
cnt++;
}
prv_b.next = prv_b.next.next;
System.out.println(cnt);
if(cnt==n)return prv_b.next;
else return head;
}
}
4.做题心得
做完之后我觉得,其实不需要用prv_b和b两个空间,只需要prv_b就可以了。 如果n刚好等于链表长度,相当于删除了head,所以需要特殊处理,于是用了一个cnt来记录。
二、两两交换链表中的节点
1.题目简介
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
2.解题思路
就把自己想成一个节点,然后去向我现在在哪,我要做什么,要怎么做(好一个3W原则)
解题步骤
-
假设我在第一个位置1,现在我需要指向下下节点:1→3
-
为了防止我指向之后丢失原本的下一节点,我需要保存原本的下一节点位置
-
然后我需要原下一节点指向当前节点:2→1
-
我需要让原本指向当前结点的去指向原下一节点:aa→2
-
最后我直接从当前节点到达我新的下一节点
-
重复步骤1-5直到后面只有一个节点或者没有节点
3.题解代码
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode aa = new ListNode();
if(head==null)return head;
if(head.next==null)return head;
ListNode a = head.next;
ListNode e = head;
while(e!=null){
if(e.next==null)return a;
if(e.next!=null){
ListNode temp = e.next;
e.next = e.next.next;
temp.next = e;
aa.next = temp;
aa = e;
e = e.next;
}
}
return a;
}
}
4.做题心得
这道题思考起来比较复杂,但是写起来其实很简单。需要注意的是步骤2和步骤4以及步骤6的判断。步骤2是用临时变量保存了原下一节点位置,步骤4是使每一次交换后的两个元素的头一个能够和前面的连接起来(因为整个过程其实是两个元素局部交换,需要和前面连通,不然就找不到前面是哪了),步骤6的判断通过e和e.next确认最后只有0个或者1个节点没有交换,就可以返回头节点了。
当然,为了防止特殊情况,对于空链表和单节点链表做了特殊判断(主要是防止边界出问题上了保险)。
三、环形链表II
1.题目简介
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
2.解题思路
首先使用快慢指针确定是否有环,如果有,再判断环的入口在哪。
判断是否有环:使用fast,slow指针,fast每次访问2个节点,slow每次访问1个节点,如果两者相遇则说明有环。
判断环的入口:如下图,设头节点head到环的入口的距离为x,环的入口到相遇节点的距离是y,从相遇节点再走到环的入口的距离是z,我们可以得到:
-
fast与slow相遇时,fast比slow多走了n圈环
则fast走过:x+y+n(y+z),slow走过x+y
-
fast的速度时slow的两倍,路程自然也是其两倍
x+y+n(y+z) = 2(x+y),
因为x是想要求的,所以得到
x = n(y+z) - y = (n-1)(y+z)+z,
x与z只相差整数倍的环长,也就是说,相遇节点和头节点一起出发,一定能在环的入口相遇!
具体原理分析请看这里:代码随想录:环找到了,那入口呢?
3.题解代码
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){
while(head!=fast){
head = head.next;
fast = fast.next;
}
return head;
}
}
return null;
}
}
4.做题心得
这道题非常值得思考,里面包含两个指针的速度关系以及路程关系,根据关系推理得到入口节点位置。如果不去自己推导只看结果的人一定会疑惑为什么相遇节点和头节点第一次相遇的位置就是环的入口?别问,问就是本人,这道题的数学推理我觉得是在线的。我做的时候,判断环是否存在是第一时间想到的,但是环的入口确实没有想到,并且这个结果可以作为结论沿用。
四、链表相交
1.题目简介
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
2.解题思路
既然两链表相交,从相交节点开始后面的节点均相同,于是将两个链表右对齐(尾部对齐),结合例子说明。
循环访问记录链表长度
将较长的链表指针向右移动链表长度差值 (实现右对齐)
3.题解代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int size_a = 0;
int size_b = 0;
for(ListNode e = headA;e!=null;e = e.next)size_a++;
for(ListNode e = headB;e!=null;e = e.next)size_b++;
if(headA == null)return headA;
if(headB == null)return headB;
if(size_a>size_b)for(int i = 0;i<size_a-size_b;i++)headA = headA.next;
else for(int i = 0;i<size_b-size_a;i++)headB = headB.next;
while(headA!=headB){
headA = headA.next;
headB = headB.next;
}
return headA;
}
}
4.做题心得
做这道题的时候主要是要想到两个链表相交之后,后面的元素应该是一样的,所以从尾部对齐是关键,当访问完所有节点后head就变为null,变成相等可以实现返回,不会形成死循环,且返回刚好符合“找不到就返回null”
今日总结
总的来说,今天的链表操作比昨天难,不是难在代码上,而是难在思路上。环形链表II需要通过逻辑推理找到环的入口;链表相交需要想到右对齐来寻找交点;删除链表倒数第N个节点需要想到通过两个节点距离差来保证前一节点走到尽头的时候后一节点能够给出删除节点的相关信息(删除节点前一位置是需要的);两两交换链表节点则是需要进行局部和整体考虑(这个倒只是需要缜密考虑,跟昨天的情况类似),不仅得考虑局部(两个节点交换),还得考虑全局(交换后与上下单元正确连接)。
知其然也要知其所以然,多对原理进行琢磨,日后遇到类似问题就利用结论去做的时候也知道其原理,会让你更加自信呀。