自己尝试去写,结果一直报错。总结原因是 对于节点更新的过程不是很明确,可能还没有将需要保存的节点信息保存下来就去更新。
看完视频后尝试利用视频的思路自己写出代码。在写代码前首先应该明确3件事情:
虚拟节点的使用
及时保存节点更新过程中可能导致丢失的节点信息
递归版本不是特别理解,可以参考一下视频b站递归方法讲解,复习的时候再理解一下。
// 迭代
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead; //指向要交换的两个节点的前一个节点
ListNode temp1 = head; // 保存要交换节点的第一个节点
ListNode temp2 = null; // 保存向要交换的两个节点的下一个节点
while(cur.next != null && cur.next.next != null){
temp1 = cur.next;
temp2 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = temp1;
temp1.next = temp2;
cur = cur.next.next;
}
return dummyHead.next;
}
}
// 递归版本
class Solution {
public ListNode swapPairs(ListNode head) {
// base case 退出提交
if(head == null || head.next == null) return head;
// 获取当前节点的下一个节点
ListNode next = head.next;
// 进行递归
ListNode newNode = swapPairs(next.next);
// 这里进行交换
next.next = head;
head.next = newNode;
return next;
}
}
双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。
题目链接:leecode删除链表的倒数第N个节点
文章讲解/视频讲解:删除链表的倒数第N个节点
要删除倒数第k个节点,那我们首先要找到要删除节点的前一个节点,要找到前一个节点,我们肯定是要循环pre = pre.next得到。所以我们要确定循环次数,所以最开始我们要知道链表中的节点个数。所以初步考虑分3步进行
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
这道题的关键就是找到要删除节点的前一个节点,用双指针来找
虚拟头节点的使用
// 快慢指针
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode = new ListNode(0);
dummyNode.next = head;
ListNode fast = dummyNode;
ListNode slow = dummyNode;
// 快指针先走n+1步
// 因为这里我们需要找到要删除节点的前一个节点即第n-1个节点
for(int i = 0; i <= n; i++){
fast = fast.next;
}
// 当fast == null时slow就指向了要删除节点的前一个节点
while(fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummyNode.next;
}
}
// 自己写的比较暴力的解法
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode cur = head;
int nums = 0;
while(cur != null) {
cur = cur.next;
nums++;
}
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
if(nums != n){
for(int i = 0; i <= nums - n - 1; i++) pre = pre.next;
}
pre.next = pre.next.next;
return dummyHead.next;
}
}
注意:数值相同,不代表指针相同。
题目链接:leecode链表相交
题目链接/文章讲解:链表相交
在牛客网剑指offer做过 JZ52 两个链表的第一个公共结点
感觉是典型的双指针的题目
使用两个指针N1,N2,一个从链表1的头节点开始遍历,我们记为N1,一个从链表2的头节点开始遍历,我们记为N2。
让N1和N2一起遍历,当N1先走完链表1的尽头(为null)的时候,则从链表2的头节点继续遍历,同样,如果N2先走完了链表2的尽头,则从链表1的头节点继续遍历,也就是说,N1和N2都会遍历链表1和链表2。
因为两个指针,同样的速度,走完同样长度(链表1+链表2),不管两条链表有无相同节点,都能够到达同时到达终点。
(N1最后肯定能到达链表2的终点,N2肯定能到达链表1的终点)。
所以,如何得到公共节点:
有公共节点的时候,N1和N2必会相遇,因为长度一样嘛,速度也一定,必会走到相同的地方的,所以当两者相等的时候,则会第一个公共的节点
无公共节点的时候,此时N1和N2则都会走到终点,那么他们此时都是null,所以也算是相等了。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA;
ListNode b = headB;
while(a != b){
a = (a == null) ? headB: a.next;
b = (b == null) ? headA: b.next;
}
return a;
}
}
双指针的应用
算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。
题目链接:leecode环形链表II
文章讲解/视频讲解:环形链表II讲解
在剑指offer做过,JZ23 链表中环的入口结点
用快慢指针遍历可以找到环的入口,但具体如何遍历忘记了,去看讲解了
需要2次遍历
第1次遍历,fast = head, slow = head ,fast一次走2步,slow一次走一步,直到两者相遇,记录第一次相遇节点。
第2次遍历,fast = head,slow = 第1次相遇的节点,二者都是每次各走1步;当fast = slow时,两者相遇,此时相遇节点即为环的入口。
判断链表是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
如果有环,如何找到这个环的入口
2次快慢指针遍历链表
while(fast != null || fast.next != null) {};
情况一:有环,一直在环里遍历
情况二:没有环,遍历到链表结尾退出while;此时应满足没有环的条件:if(fast == null || fast.next == null) return null;
// 代码和老师的有一点不一样,主要是多了一个if(fast == null || fast.next == null) return null;,再寻找第二次相遇点
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)
break;
}
//若是快指针指向null,则不存在环
// 这个边界条件可能考虑不全
if(fast == null || fast.next == null) return null;
//其中一个指针重新指向链表头部
slow = head;
// 与第一次相遇的结点相同速度出发,相遇结点为入口结点
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}