24. 两两交换链表中的节点
题目链接
代码随想录讲解
思路:主体是三个指针:例如:0—>1—>2—>3—>4,交换2,3:temp指向1,left和fast指向2
fast指向3;
left.next指向4即2—>4;
fast.next指向left,即3—>2;
temp.next指向fast,即1–>3;
交换完成:1—>3—>2—>4;
最后移动指针,temp指向left也就是2,right指向left.next也就是4,left指向right。
加入虚拟头节点,从virhead开始可以让交换原头节点和第二个节点的过程和后面的一样。
我写的时候因为有virhead,所以直接将head作为left了
class Solution {
public ListNode swapPairs(ListNode head) {
//如果只有一个节点或者没有节点则直接返回
if(head==null||head.next==null)return head;
//创建虚拟头节点
ListNode virHead=new ListNode(-1);
virHead.next=head;
//通过三个节点指针交换
ListNode temp=virHead;
ListNode right=head;
while(head!=null&&head.next!=null){
right=head.next;
head.next=right.next;
right.next=head;
temp.next=right;
temp=head;
right=head.next;
head=right;
}
return virHead.next;
}
}
19.删除链表的倒数第N个节点
题目链接
代码随想录讲解
思路:
删除倒数第n个节点,因为链表是单向的,所以只能从头遍历,定义两个指针,快指针先移动n次,再同步移动慢指针,最后慢指针指向的是需要删除节点的前一个节点。
代码随想录中说fast移动n+1步,其实和fast最后判断有关,假如fast指向null为终止条件,就是n+1步,假如fast.next==null为终止条件,就是n步,fast最后指向链表最后一个节点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head==null||head.next==null)return null;
//需要虚拟头节点
ListNode virhead=new ListNode();
virhead.next=head;
ListNode left=virhead;
ListNode right=virhead;
int k=0;
//快指针移动n次
while(right.next!=null&&k!=n){
right=right.next;
k++;
}
if(k==n){
//同步移动
while(right.next!=null){
right=right.next;
left=left.next;
}
//删除节点
left.next=left.next.next;
}
return virhead.next;
}
}
160.链表相交
题目链接
代码随想录
思路:
三种情况:1、A,B等长有公共部分;2、A,B不等长有公共部分;3、A,B没有公共部分
情况一:从headA和headB同时出发,每走一步判定是否为同一个节点;
情况二:假如A比B长n个节点,则A先走n个节点后,B再同时走,回到情况一;
情况三:因为情况二会回到情况一,即情况一走完都没有公共节点。
代码:从headA和headB同时走,若等长,则同时到达公共节点,或者同时到达尾节点;若不等长,一定有一个先到尾节点,假设是tempA先到达尾节点,则B比A长,此时让tempA转向headB,在tempB到达尾节点时,tempA在B上已经移动了B-A的次数,此时让tempB转向headA,则tempA和tempB在相同长度链表上移动,一定同时到达公共节点或者尾节点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//为空就不可能有公共节点
if(headA==null||headB==null)return null;
ListNode tempA=headA;
ListNode tempB=headB;
//判断是否为同一节点
while(tempA!=tempB){
if(tempA.next==null){
tempA=headB;
}else{
tempA=tempA.next;
}
if(tempB.next==null){
tempB=headA;
}else{
tempB=tempB.next;
}
//两个节点同时走到尾节点也没找到公共节点
if(tempA.next==null&&tempB.next==null&&tempA!=tempB)return null;
}
return tempA;
}
}
142.环形链表II
题目链接
代码随想录
思路:没想出来,看的卡哥的题解。之前在牛客网上刷过类似的题,但那道可以修改链表,通过创建一个空链表作为标记,当找到next指向空链表的节点时,即为入口。这道题不允许修改链表。就只能通过快慢指针的方式了。
判断链表是否环
如果有环,如何找到这个环的入口
判断链表是否有环:
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
如果有环,如何找到这个环的入口:
(建议直接看curl哥的讲解,配上图好理解一些)
fast指针一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
主要理解为什么slow是x+y,fast是x+n*(y+z);
slow入环时,fast一定在环内某个位置,其实可以用两个环来表示,slow周期为2T,fast周期为T,在2T内一定会相遇一次。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow=head;
ListNode fast=head;
//判断是否有环
while(fast!=null&&slow.next!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
//环存在
if(slow==fast){
fast=head;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
return null;
}
}
链表总结
虚拟头节点:让循环操作一致,而不用对原头节点作单独处理。
链表的基本操作:(虽然自己写出来了,但是第一遍bug很多,主要是根据索引插入和删除,怎么找到索引的位置,当索引非法怎么处理)
获取链表第index个节点的数值
在链表的最前面插入一个节点
在链表的最后面插入一个节点
在链表第index个节点前面插入一个节点
删除链表的第index个节点的数值
反转链表、删除倒数第N个节点、链表相交、环形链表:虚拟头节点以及双指针的运用。
环形链表中的数学方法还需要后面再理一理。