leetcode24. 两两交换链表中的节点
这题和反转链表有相似之处,同样可以用循环模拟做出,也能用递归法。
注意点:
- 虚拟头节点;
- 画图理解题目以及写代码,明白了一次交换会涉及到4个节点的参与,那么就容易用代码实现了;
- 先写简单好懂的模拟法,再写递归法,便于理解吸收。
class Solution {
// 法一 模拟法(非递归)
public ListNode swapPairs(ListNode head) {
// 特判(由题意)
if (head == null || head.next == null) {
return head;
}
//
ListNode dumyHead = new ListNode(-1); // 设置一个虚拟头结点
dumyHead.next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode cur = dumyHead; // 指针cur总是指向两个节点的前一个节点(start节点的前一个)
ListNode tmp; // 临时节点,保存两个节点后面的节点(end节点的后一个)
ListNode start; // 临时节点,保存两个节点之中的第一个节点
ListNode end; // 临时节点,保存两个节点之中的第二个节点
while (cur.next != null && cur.next.next != null) {
// 赋值(确定一开始的位置)
tmp = cur.next.next.next;
start = cur.next;
end = cur.next.next;
// 一次两两交换(涉及到4个节点,弄清步骤顺序)
cur.next = end; // 步骤一
end.next = start; // 步骤二
start.next = tmp; // 步骤三
// 一次两两交换结束,指针cur移动到下一次交换的第一个节点上,准备下一轮交换
cur = start;
}
return dumyHead.next;
}
}
这题的递归法主要参考了力扣上的画解算法,并结合自己在本地IDE上逐步debug才能较好的理解递归(桟或树)。
递归写法要观察本级递归的解决过程,形成抽象模型,因为递归本质就是不断重复相同的事情。而不是去思考完整的调用栈,一级又一级,无从下手。如图所示,我们应该关注一级调用小单元的情况,也就是单个 f(x)。
其中我们应该关心的主要有三点:
返回值
调用单元f(x)做了什么
终止条件
在本题中:
返回值:交换完成的子链表(从后往前);
调用单元f(x):设需要交换的两个点为 head 和 next,head 连接后面交换完成的子链表,next 连接 head,完成交换;
终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,无法进行交换;
class Solution {
// 法二 递归法
public ListNode swapPairs(ListNode head) {
// 特判(由题意, base case)
// 也是递归调用的终止条件:head 为空指针或者 next 为空指针,也就是当前无节点或者只有一个节点,则无法进行交换
if (head == null || head.next == null) {
return head;
}
// 获取当前节点的下一个节点
ListNode next = head.next;
// 进行递归(newNode是返回值:交换完成的子链表)
ListNode newNode = swapPairs(next.next);
// 这里进行交换(调用单元f(x):next 指向 head,head 指向后面交换完成的子链表,即完成了交换)
next.next = head;
head.next = newNode;
// 返回(子)链表
return next;
}
}
python递归法(简单易懂,画图)
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 递归的终止条件是链表中没有节点,或者链表中只有一个节点,此时无法进行交换
if not head or not head.next:
return head
# 给节点标号
one = head
two = one.next
three = two.next
# 节点交换
two.next = one
one.next = self.swapPairs(three)
# 返回新链表的头结点
return two
leetcode19. 删除链表的倒数第N个节点
这题基本可以直接写出来。
注意点:
- 快慢指针的双指针经典应用;
- 需要让指针 slow 指向待删除节点的前一个节点,才能做删除操作;
- 最后返回头结点head时,应该写成dumyHead.next
class Solution {
// 双指针的经典应用,快慢指针法
public ListNode removeNthFromEnd(ListNode head, int n) {
// 虚拟头结点
ListNode dumyHead = new ListNode(-1);
dumyHead.next = head;
// 快慢指针
ListNode fast = dumyHead;
ListNode slow = dumyHead;
// 快慢指针需要相差 n 个结点(如fast指向3节点,而slow指向虚拟头节点,中间隔着1和2节点)
// 因为需要让指针 slow 指向待删除节点的前一个节点
// 因此 n >= 0
while (n >= 0) {
fast = fast.next;
n--;
}
// 同时移动,直到fast指向null,此时 slow 的位置就是待删除元素的前一个位置
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 删除节点
slow.next = slow.next.next;
// 返回头结点head(注意:这里如果直接写head,遇到某些样例时,会产生错误的结果;因此最好写成dumyHead.next)
//return head;
return dumyHead.next;
}
}
python:
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
# 创建一个虚拟节点,并将其下一个指针设置为链表的头部
dummy_Head = ListNode()
dummy_Head.next = head
# 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
fast = dummy_Head
slow = dummy_Head
# 快指针比慢指针快 n+1 步
for i in range(n + 1):
fast = fast.next
# 移动两个指针,直到快指针到达链表的末尾
while fast:
fast = fast.next
slow = slow.next
# 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
slow.next = slow.next.next
# 返回虚头的下一个节点,即新链表的头结点
return dummy_Head.next
leetcode160. 链表相交
注意点:
- 等比例法
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//
if (headA == null || headB == null) {
return null;
}
//
ListNode curA = headA;
ListNode curB = headB;
//
while (curA != curB) {
//
if (curA.next == null) {
curA = headB;
} else {
curA = curA.next;
}
//
if (curB.next == null) {
curB = headA;
} else {
curB = curB.next;
}
//
if (curA.next == null && curB.next == null && curA != curB) {
return null;
}
}
return curA;
}
}
python等比例法
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
# 处理边缘情况
if not headA or not headB:
return None
# 在每个链表的头部初始化两个指针
pA = headA
pB = headB
# 遍历两个链表直到指针相交
while pA != pB:
# 将指针向前移动一个节点
pA = pA.next if pA else headB
pB = pB.next if pB else headA
# 如果相交,指针将位于交点节点,如果没有交点,值为None
return pA
leetcode142. 环形链表II
- 画图分析理解(看代码注释)
- break语句用于跳出当前所在的循环体,立即结束循环,并执行循环后面的代码(跳出整个循环)。
- continue语句用于终止本次循环的剩余部分,并进入下一次循环的迭代(提前结束,进入下一次循环)。
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 创建快慢双指针
fast, slow = head, head
# 双指针第一次相遇
while True:
# 第一种结果: fast 指针走过链表末端,说明链表无环,直接返回 null;
if not (fast and fast.next): return
# 第二种结果: 当fast == slow 时,两指针在环中第一次相遇
fast, slow = fast.next.next, slow.next
if fast == slow: break
# 将fast指针重新指向链表头部节点,slow指针位置不变
fast = head
# 双指针第二次相遇
while fast != slow:
fast, slow = fast.next, slow.next
# 返回两个指针指向的节点
return fast