[LeetCode] 24. 两两交换链表中的节点
核心思想是使用三个节点记录住需要交换的两个节点, 以及这两个节点之前的一个节点. 因为要交换的两个节点交换完顺序后, 他们之前的节点需要链接到这两个节点的后者, 比如原来的顺序是 ... -》A-》B-》C, B 和 C 交换完之后, 需要将 A 指向 C, 即变成 ... -》A -》C -》B.
同时如果不使用虚拟头节点的话, 需要判断的逻辑就非常多. 如果需要对链表做增加、删除、修改操作的话, 使用虚拟头节点会更方便.
/**
* 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) {
if (head == null) {
return null;
}
ListNode result = null;
ListNode pre = head;
ListNode current = head.next;
ListNode next;
while (current != null) {
next = current.next;
current.next = pre;
if (result == null) {
result = current;
}
if (next != null) {
if (next.next != null) {
pre.next = next.next;
} else {
pre.next = next;
}
pre = next;
current = next.next;
} else {
pre.next = null;
current = null;
}
}
return result == null ? pre : result;
}
}
/**
* 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) {
if (head == null) {
return null;
}
// 使用虚拟头节点
ListNode dummy = new ListNode(0, head);
ListNode pre = dummy;
ListNode current = dummy.next;
ListNode next = null;
while (current != null && current.next != null) {
next = current.next;
pre.next = next;
current.next = next.next;
next.next = current;
pre = current;
current = current.next;
}
return dummy.next;
}
}
[LeetCode] 19. 删除链表的倒数第 N 个结点
这一题完全忘记了, 如何知道倒数第 N 个节点是谁呢? 这里有一个巧妙的思想, 利用双指针, slowIndex 指向虚拟头节点, fastIndex 指向从虚拟头节点往第 N 个节点, 不包括虚拟头节点. 然后将 slowIndex 和 fastIndex 同时往后移动, 直到 fastIndex 移动到最后一个节点, 遇到 fastIndex.next == null 就停止移动. 这时候 slowIndex 指向的是倒数第 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(0, head);
ListNode fastNode = dummyNode;
ListNode slowNode = dummyNode;
while (n-- > 0 && fastNode != null) {
fastNode = fastNode.next;
}
if (fastNode == null) {
return dummyNode.next;
}
while (fastNode.next != null) {
fastNode = fastNode.next;
slowNode = slowNode.next;
}
if (slowNode.next != null) {
slowNode.next = slowNode.next.next;
}
return dummyNode.next;
}
}
[LeetCode] 面试题 02.07. 链表相交
这一题比较简单, 如果链表相交, 则从相交的节点开始往后的节点都是一致的, 因此从相交的节点开始的链表长度也是一致的. 因此如果两个链表长度不一致, 则可以删除长度更长的链表多出来的那些节点, 然后再从头比对两个链表, 直到找到相同的节点即可.
/**
* 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) {
if (headA == null || headB == null) {
return null;
}
int lengthA = 0;
int lengthB = 0;
ListNode head = headA;
while (head != null) {
lengthA++;
head = head.next;
}
head = headB;
while (head != null) {
lengthB++;
head = head.next;
}
if (lengthA > lengthB) {
for (int i = 0; i < lengthA - lengthB; i++) {
headA = headA.next;
}
} else if (lengthA < lengthB) {
for (int i = 0; i < lengthB - lengthA; i++) {
headB = headB.next;
}
}
while (headA != null) {
if (headA == headB) {
return headA;
} else {
headA = headA.next;
headB = headB.next;
}
}
return null;
}
}
[LeetCode] 142. 环形链表 II
二刷的时候环形链表就比较简单了.
第一步判断是会否有环: 使用快慢节点(双指针思想), 快节点每次走两步: fastNode = fastNode.next.next, 慢节点每次走一步: slowNode = slowNode.next. 如果 fastNode 走到链表的结尾, 说明没有环. 如果 slowNode == fastNode 则说明有环.
第二步寻找环的入口: 假设起点到环的入口节点数量为 x, 环的入口到快慢节点相遇的地方节点数为y, 快慢节点相遇的地方绕着环抵达环的入口一共有 z 个节点. 我们可以得到 2 * (x + y) = x + y + m * (y + z), 其中 m 为快节点和慢节点相遇时, 绕环的圈数. 合并同类项可得: x = m * (y + z) - y, 对等式进行变换可得 x = m * (y + z) - y - z + z, 继续变换可得 x = (m - 1) * (y + z) + z, 也就是说“如果一个节点从快慢节点相遇的地方继续绕着环走, 一个节点从出发点出发, 每次走一个节点, 他们相遇的位置就是环的入口. 这里 x 就是从起点出发到环的入口的节点数, (m - 1) * (y + z) + z 是从环的快慢节点相遇的地方出发, 绕 m - 1 圈后, 继续走 z 个节点, 由 z 的定义可知, 绕 m - 1 圈后再走 z 个节点, 这时候是抵达到环的入口的.
/**
* 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 fastNode = head;
ListNode slowNode = head;
boolean hasCircle = false;
while (fastNode != null && fastNode.next != null) {
slowNode = slowNode.next;
fastNode = fastNode.next.next;
if (slowNode == fastNode) {
break;
}
}
if (fastNode == null || fastNode.next == null) {
// 走到末尾了, 没有环
return null;
}
while (head != fastNode) {
head = head.next;
fastNode = fastNode.next;
}
return head;
}
}