代码随想录算法训练营第四天 | 24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题(02.07)链表相交、142.环形链表II
24.两两交换链表中的节点
虚拟头节点法
思路:设置一个虚拟头节点,统一头节点和其他节点的操作。 在做题时建议画图,因为涉及到多个指针,容易混乱。对于本题,最后返回的头节点是 dummyNode.next。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummyNode = new ListNode(0); //设置虚拟头节点
dummyNode.next = head; //链接虚拟头节点和原头节点,方便后续操作
ListNode cur = dummyNode;
//while循环内条件分别代表偶数个节点和奇数个节点的情况,
//且顺序不能调换,否则会导致空指针异常
while (cur.next != null && cur.next.next != null){
ListNode temp1 = cur.next; //记录临时节点1
ListNode temp2 = cur.next.next.next; //记录临时节点2
cur.next = cur.next.next; //步骤一
cur.next.next = temp1; //步骤二
cur.next.next.next = temp2; //步骤三
cur = cur.next.next; //cur移动两位,准备下一轮交换
}
return dummyNode.next;
}
}
19.删除链表的倒数第N个节点
虚拟头节点 + 快慢指针法
思路:本题关键点在于如何找到倒数第 n 个节点。 虚拟头节点的作用在于统一操作,快慢指针的作用在于方便寻找倒数第 n 个节点的前一个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyNode = new ListNode(-1); //虚拟头节点
dummyNode.next = head;
//设置快、慢指针
ListNode fast = dummyNode;
ListNode slow = dummyNode;
//让快指针先走n + 1步,
//旨在当fast指向尾节点的后一位(null)时,slow恰好指向待删除节点的前一个节点
while (n-- != 0 && fast != null){
fast = fast.next;
}
fast = fast.next; //快指针多走1步
//快、慢指针同时移动
while (fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next; //删除节点
return dummyNode.next;
}
}
160. 相交链表(面试题 02.07)
虚拟头节点 + 快慢指针法
思路:本题关键点在于:相交节点不是指节点的值相等,而是指节点在内存中的位置相等。 主要过程为求出两链表长度的差值,然后将长链表与短链表尾部对齐,之后便可以比较 cur1 和 cur2。如果两指针相同则返回任一节点,否则同时向后移动 cur1 和 cur2。循环至两指针指向 null 时,则说明两个链表不存在相交节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode cur1 = headA;
ListNode cur2 = headB;
int len1 = 0, len2 = 0; //用于统计两链表的长度
while (cur1 != null) { //求链表A的长度
len1++;
cur1 = cur1.next;
}
while (cur2 != null) { //求链表B的长度
len2++;
cur2 = cur2.next;
}
//重置cur1和cur2
cur1 = headA;
cur2 = headB;
//保证cur1指向较长的链表
if (len2 > len1) {
int tempLen = len1;
len1 = len2;
len2 = tempLen;
ListNode tempNode = cur1;
cur1 = cur2;
cur2 = tempNode;
}
int gap = len1 - len2; //求长度差
//让cur1和cur2在同一起点上(末尾位置对齐)
while (gap-- > 0) { //此处 != 和 > 均可
cur1 = cur1.next;
}
//比较curA和curB是否相同,如果相同则返回任一节点;
//否则同时向后移动cur1和cur2
while (cur1 != null) {
if (cur1 == cur2) {
return cur1;
}
cur1 = cur1.next;
cur2 = cur2.next;
}
//如果两个链表不存在相交节点,返回 null 。
return null;
}
}
142. 环形链表 II
快慢指针法
思路:本题主要考察对链表的操作和一些数学运算。 要点如下:
- 判断链表是否有环;
- 如果有环,如何找到环的入口。
①判断链表是否有环
使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast 指针每次移动两个节点,slow 指针每次移动一个节点,如果 fast 和 slow 指针在途中相遇 ,说明这个链表有环。原因在于相对于 slow 指针来说,fast 指针是一个节点一个节点靠近 slow 指针的,因此二者一定会相遇。
②寻找环的入口
假设从头结点到环形入口节点的节点数为 x,环形入口节点到 fast 指针与 slow 指针相遇节点 节点数为 y,从相遇节点再到环形入口节点节点数为 z。 如图所示:
(1)快慢指针相遇时,slow 指针走过的节点数为: x + y, fast 指针走过的节点数:x + y + n (y + z),n代表 fast 指针在环内走了 n 圈才遇到 slow 指针,(y + z)为一圈内节点的个数。
(2)因为 fast 指针是一步走两个节点,slow 指针一步走一个节点, 所以 fast 指针走过的节点数 = slow 指针走过的节点数 * 2,即 (x + y) * 2 = x + y + n (y + z),两边消掉一个(x + y),即 x + y = n (y + z)。因为 x 表示头结点到环形入口节点的的距离,因此移项将 x单独放在等号左边:x = n (y + z) - y,再从 n(y + z) 中提出一个(y + z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z。此处 n 一定是 >=1 的,因为 fast 指针至少要多走一圈才能遇到 slow 指针。
这个公式说明:
- 对于 n = 1 的情况,意味着 fast 指针在环形里转了一圈之后,就遇到了 slow 指针。当 n 为1的时候,公式就化解为 x = z,这就意味着,从头结点处和相遇节点处各出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。也就是在相遇节点处,定义一个指针 index1,在头结点处定一个指针 index2。让 index1 和 index2 同时移动,每次移动一个节点, 那么他们相遇的地方就是环形入口的节点。
- 对于 n > 1 的情况,意味着 fast 指针在环中转 n 圈之后才遇到 slow 指针。而这种情况和n = 1的时的效果是一样的,只不过 index1 指针在环里多转了 (n-1) 圈,然后再遇到 index2 指针,相遇点依然是环形的入口节点。
③为什么 slow 指针在环中的第一圈就会遇到 fast 指针?
当 slow 指针进环的时候,fast 指针一定是在环的任意一个位置,如图所示:
则 fast 指针走到环入口3的时候,已经走了 k + n 个节点,slow 指针相应的应该走了 (k + n) / 2 个节点。因为 k 是小于 n 的(图中可以看出),所以 (k + n) / 2 一定小于 n。也就是说 slow 指针一定没有走到环入口3,而 fast 指针已经到环入口3了,即在 slow 指针开始走的那一环已经和 fast 指针相遇了。而 fast 指针相对于 slow 指针是一次移动一个节点,所以不可能跳过去。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = 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) {
ListNode index1 = fast;
ListNode index2 = head;
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}