题目与题解
24. 两两交换链表中的节点
题目链接:24. 两两交换链表中的节点
代码随想录题解:24. 两两交换链表中的节点
解题思路:
同样,由于这道题涉及到链表增删,设置虚拟头结点有助于简化后续的操作。
题目思路很简单,假设当前结点为cur,要交换的结点分别是cur.next和cur.next.next,为了方便记忆,这两个结点起名为n1,n2,并将n2.next起名为n3。
那么,实际操作的顺序是,使cur.next=n2,n2.next=n1,最后n1.next=n3,n1和n2就交换完成了,而n1前和n2后的结点顺序不会发生改变。接着向后移动两次cur结点,重复上述操作。
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode virHead = new ListNode(0, head);
ListNode cur = virHead;
while (cur.next != null && cur.next.next != null) {
ListNode n1 = cur.next;
ListNode n2 = cur.next.next;
ListNode n3 = cur.next.next.next;
cur.next = n2;
n2.next = n1;
n1.next = n3;
cur = n1;
}
return virHead.next;
}
}
看完代码随想录之后的想法
思路是一致的,链表题画图非常重要,贴上代码随想录的图方便理解。
随想录给出的代码会更简化一点,不过对我来说容易搞错,所以我还是另外设置了几个变量用于记录。
遇到的困难
脑子清醒,图一次画对,没有困难:)
19.删除链表的倒数第N个节点
题目链接:19.删除链表的倒数第N个节点
代码随想录题解:19.删除链表的倒数第N个节点
解题思路:
基础思路就是先遍历一遍链表,记录链表长度len,那么倒数第n个结点就是正数第len-n个结点,删除即可。同样涉及到增删,用虚拟头结点更方便。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 两次遍历
int len = 0;
ListNode cur = head;
while (cur != null) {
++len;
cur = cur.next;
}
ListNode virHead = new ListNode(0, head);
cur = virHead;
for (int i = 0; i < len - n; i++) {
cur = cur.next;
}
cur.next = cur.next.next;
return virHead.next;
}
}
题目要求用一遍扫描实现,这时只需要再设置前后两个结点,当前结点从头向后走了n步后,后一个结点再出发,二者同时前进,直到前面的结点走到了链表末尾,后面结点所在位置就是要删除的结点位置。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
// 一次遍历
ListNode virHead = new ListNode(0, head);
ListNode p1 = virHead;
for (int i = 0; i < n; i++) {
p1 = p1.next;
}
ListNode p2 = virHead;
while (p1.next != null) {
p1 = p1.next;
p2 = p2.next;
}
p2.next = p2.next.next;
return virHead.next;
}
}
看完代码随想录之后的想法
写链表时,需要注意到合适的边界条件,要对当前结点进行操作时,很多时候要通过前一个结点进行。我做的时候为了方便,都是写完之后用一个例子来判断,遍历的边界有没有超出,当前元素是否正确。这是有点投机取巧的方法,对于简单过程很有效。
我写的时候看到题目说n>=1小于等于链表长度,所以在前一个结点遍历时没有判断其是否为空,面试时有的时候不会给你这么多这么好的边界条件,所以平时能加上要尽量加上。
遇到的困难
这道题以前做过很多遍了,还记得,不难。
160. 链表相交
题目链接:160. 链表相交
代码随想录题解:160. 链表相交
解题思路:
如果两个链表最后相交,那么从相交的结点开始,其后面的所有结点都应该相同。这里可以设置两个指针,分别去遍历两个链表,得到两个链表的长度lenA和lenB;然后一个指针再从长一点的链表从头出发,走了|lenA-lenB|步后,另一个指针再从短一点的链表头出发,两者同步前进,直到遍历得到的结点相同则返回当前结点;如果没有相同结点,指针就会行进到链表的末尾,返回null即可。
因为不知道lenA和lenB哪个长,为了方便调用,写了一个新的函数getNode专门用来处理。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int lenA = 0;
ListNode cur = headA;
while (cur != null) {
++lenA;
cur = cur.next;
}
int lenB = 0;
cur = headB;
while (cur != null) {
++lenB;
cur = cur.next;
}
if (lenA > lenB) return getNode(lenA - lenB, headA, headB);
else return getNode(lenB - lenA, headB, headA);
}
public ListNode getNode(int n, ListNode longList, ListNode shortList) {
ListNode p1 = longList;
for (int i = 0; i < n; i++) {
p1 = p1.next;
}
ListNode p2 = shortList;
while (p1 != null && p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
}
看完代码随想录之后的想法
思路一致,写法上随想录根据情况交换A和B,固定了A是更长的链表,代码更少,不需要额外的函数,内存占用更少一点。
遇到的困难
这道题同样以前做过无数次了,思路非常清晰,所以写起来也很容易。不断的重复就能加深记忆!
142.环形链表II
题目链接:142.环形链表II
代码随想录题解:142.环形链表II
视频讲解:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili
解题思路:
环形链表的解题思路,一般是先判断是否有环,然后根据环形链表的特征,写出相应的数学公式,再落实到代码上处理。
判断是否有环很简单,设置两个指针fast和slow,二者同时从头出发,slow一次走一步,fast一次走两步,如果链表中存在环,那二者一定会相遇。
但是这道题要求找到环形链表的入口,自己写了半天公式也写不对,只能去看代码随想录的答案。其实代码本身非常容易,但是公式难求。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) break;
}
if (fast == null || fast.next == null) return null;
ListNode index1 = head, index2 = slow;
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
看完代码随想录之后的想法
最重要的是如何根据快慢指针跑的速度和相遇的位置,来判断入口究竟有多远。这张图是一个很好的参考。
快慢指针相遇时,快指针跑了x+y+n(y+z)步,n表示快指针重复跑的圈数,慢指针跑了x+y步,而快指针实际走的路程是慢指针的两倍,即2(x+y),那么
x+y+n(y+z)=2(x+y)
我们要求的是x,那么变换一下公式,得到x的表达式
x=(n-1)(y+z)+z
y+z就是一圈,一个指针在环中走了n圈,其所在结点还是它出发的位置。那么假设一个指针从头走了x步到达环形入口,就相当于另一个指针从相遇结点出发后,走了(n-1)圈加上z步的所在位置。此时就可以设置两个指针,一个从头出发,一个从相遇结点出发,它们最后会在入口相遇。
关于慢指针为什么只跑了x+y,而不是x+y+若干环的长度,我一开始也不太理解。解答也是很有数学之美。由于fast更快,当slow进环时,fast必然已经在环里面了。假设环长度是l,fast在slow进环时t距离环入口k步,k<l。接着,当fast第二次走到环入口时,fast走了k+l步,此时slow走了(k+l)/2步,结果一定小于l,也就是说,fast第二次走到环入口之前,必然碰到过slow。
或者换个角度想,当slow进环后,如果slow走了一圈再次到达入口,此时slow走了l步,fast走了2l步,也就是说fast已经套圈slow了,二者肯定遇到过。
这道题非常有巧思,有点难想,记住吧。
遇到的困难
这道题也不止做过一遍,但是没有想出来公式究竟应该怎么写。即使这次看完随想录的答案,我也不敢保证说下一次碰到能想出正确的结果。只能记住了。
今日收获
今天的题都是以往做过很多次的,所以写起来难度不大,但前提是必须知道正确的解法。实际做题大概两小时,环形链表花了一个小时也没有做出来,有点沮丧。
到今天为止,链表的基础题就算过了一遍。链表的tips总结如下:
- 涉及到链表增删的,巧用虚拟头结点
- 遍历链表时注意边界条件,防止空指针异常
- 想清楚究竟应该从哪个结点开始操作, 是pre还是cur还是next
- 双指针法在链表中适用于只想遍历一或几次的情况