代码随想录Day04|两两交换链表中的结点 删除链表的倒数第N个结点 链表相交 环形链表II
1.题目链接:两两交换链表中的节点 - 力扣
题目描述:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
解题思路:
这道题在自己一开始思考的时候,没有想到使用虚拟头结点的方法。大体的思路是有的,就是比较难确定循环终止的条件,奇数个点和偶数个点没有适用的情况。后面去看了下文章的思路,发现使用虚拟头结点的话,就可以统一情况来讨论。
让当前指针cur指向要交换的两个结点的前一个结点,如果结点是奇数结点,那么循环结束的条件是cur.next.next=null,如果是偶数个结点的话结束的条件应为cur.next.next.next=null,从而可以写出while循环,while(cur.nextnull&&cur.next.nextnull)。这里需要特别注意的是,while的前后条件不能够调换,调换的话可能会发生空指针异常的情况。
接着需要使用一个变量来保存需要交换的第一个结点以及一个变量来保存第二个结点的下一个结点。也就是需要保存需要交换的第二个结点的有一个和后一个结点。前期做好准备后,我们就可以开始进行指针的交换了。
交换完后我们还需要注意返回值不是head,而是虚拟结点的下一个结点,即dummy.next
代码示例如下:
class Solution {
public ListNode swapPairs(ListNode head) {
//创建虚拟头结点
ListNode dummy=new ListNode(-1,head);
ListNode cur=dummy;
while(cur.next!=null&&cur.next.next!=null){
ListNode temp=cur.next;
ListNode temp1=cur.next.next.next;
cur.next=cur.next.next;
cur.next.next=temp;
temp.next=temp1;
cur=cur.next.next;
}
return dummy.next;
}
}
2.题目链接:删除链表的倒数第 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]
解题思路:
1.暴力解法
一开始没有想到使用快慢指针,用的是暴力解法。需要把链表完全遍历完。先添加一个虚拟头结点,使得删除虚拟结点的方式统一。接着完全遍历链表,用一个变量i来计数,看下一共有多少个结点。接着采用减法,便可知道是正数的第几个结点,再按照删除结点的方式,找到要删除的结点的前驱结点即可。
下面是代码示例:
class Solution2 {
public ListNode removeNthFromEnd(ListNode head, int n) {
if(head==null){
return head;
}
ListNode dummy=new ListNode(-1,head);
ListNode cur=head;
ListNode pre=dummy;
ListNode count=dummy;
int i=0;
while(count!=null){
i++;
count=count.next;
}
for(int j=i-n-1;j>0;j--){
cur=cur.next;
pre=pre.next;
}
pre.next=cur.next;
return dummy.next;
}
}
2.双指针解法
这道题也可以使用双指针的方法去解。要删除倒数第n个结点,让快指针 r先移动n+1步,然后快慢指针同时移动,当r移动到链表的末尾是,删掉慢指针s所指向的后一个结点即可。
下面是代码示例:
class Solution{
public ListNode removeNthFromEnd(ListNode head, int n){
//利用快慢指针来找出倒数的指针
ListNode dummy=new ListNode(-1,head);
ListNode r=dummy;
ListNode l=dummy;
for(int i=0;i<=n;i++){
r=r.next;
}
while(r!=null){
r=r.next;
l=l.next;
}
l.next=l.next.next;
return dummy.next;
}
}
3.题目链接:链表相交 - 力扣
题目描述:给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
解题思路:
在遇到相同的交点之前,A和B的起点位置不一样。我一开始的想法是直接一直往后,往后到相同的即返回。后面发现这种方法不可行,因为起始点的个数不一样。为了解决起始点个数不一样的问题,选择了计算出链表A和链表B的长度,然后再看它们之间的长度差,让长的那一个提前往前走几步,使得两个链表的起点在同一个位置,如下图所示:
之后再一一进行比较即可。
下面是代码示例:
public class Solution {
//求出长度差,然后进行遍历
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA=headA;
ListNode curB=headB;
int lenA=0,lenB=0;
while(curA!=null){
lenA++;
curA=curA.next;
}
while(curB!=null){
lenB++;
curB=curB.next;
}
//计算完后记得调回到表头
curA=headA;
curB=headB;
//让curA为最长链表的头,lenA为其长度
if(lenB>lenA){
int temp=lenA;
lenA=lenB;
lenB=temp;
ListNode temp1=curA;
curA=curB;
curB=temp1;
}
int gap=lenA-lenB;
while(gap-->0){
curA=curA.next;
}
while(curA!=null){
if(curA==curB){
return curA;
}
curA=curA.next;
curB=curB.next;
}
return null;
}
}
4.题目链接:环形链表 II - 力扣
题目描述:给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
解题思路:
要找到是否有环,需要定义快慢指针。定义快指针fast,fast每次前进两步。定义慢指针slow,slow每次前进一步。
如果没有环,则fast会走到null,则返回null。
如果有环,fast一定会比slow先进入环中绕圈,当slow进入环后,快指针每次都比慢指针多走一步,也就是以一步的速度来追慢指针,只要是在环内,块指针一定可以在慢指针走完一圈前和慢指针相遇。
得到指针是一个环后,我们就需要去找到进入环的位置,这个时候就需要用到一点数学的知识来找进入环的点。如下图:
这个时候知道了完整的思路,代码就很好写出来了
下面是代码示例:
class Solution4{
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){
ListNode indexA=fast;
ListNode indexB=head;
while(indexA!=indexB){
indexA=indexA.next;
indexB=indexB.next;
}
return indexA;
}
}
return null;
}
}
总结:
今天的所有题目都不算很难,但都值得仔细的去推敲。亮亮交换链表中的节点时需要注意的是,可以创建一个虚拟头结点,这样有利于统一条件往下循环。同时需要注意保存变量和发生循环条件的顺序,避免发生空指针异常。
第二道题是用双指针的方法会比较快且简单,一开始只想到了暴力解法,对于双指针的运用还不是很熟悉,需要再去看一下已经学习了的运用到双指针的题目,多练习总结,让自己更好的掌握整个部分的知识点。
第三道题则是需要算出两个链表的长度差,使他们在同一个起点开始(描述的可能不太准确,但看上面的图就能够知道了)。这里需要注意找完长度差后,要记得把curA和curB分别指向各自的头结点,不然后面都会出错,
第四道题是以前不太敢碰的题目,看到环形都会有点退缩,今天仔细思考和学习了之后发现并不难。同时知道了找是否有环,可以使用快慢指针的方法,一个走两步,一个走一步,在环内一定可以相遇。
换链表中的节点时需要注意的是,可以创建一个虚拟头结点,这样有利于统一条件往下循环。同时需要注意保存变量和发生循环条件的顺序,避免发生空指针异常。
第二道题是用双指针的方法会比较快且简单,一开始只想到了暴力解法,对于双指针的运用还不是很熟悉,需要再去看一下已经学习了的运用到双指针的题目,多练习总结,让自己更好的掌握整个部分的知识点。
第三道题则是需要算出两个链表的长度差,使他们在同一个起点开始(描述的可能不太准确,但看上面的图就能够知道了)。这里需要注意找完长度差后,要记得把curA和curB分别指向各自的头结点,不然后面都会出错,
第四道题是以前不太敢碰的题目,看到环形都会有点退缩,今天仔细思考和学习了之后发现并不难。同时知道了找是否有环,可以使用快慢指针的方法,一个走两步,一个走一步,在环内一定可以相遇。
今天的收获还是挺多的,后面还需要再次进行回顾,更加熟练的掌握一些题目比较典型的解法。