系列文章目录
【链表系列01】反转链表
【链表系列02】两两交换链表中的节点
文章目录
1. 题目概述
1.1. 题目内容
题目来源:力扣(Leetcode) - 第24题,单向链表问题。
难度等级:中等。
题目内容:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点,如下图所示。
1.2. 题目解析
本题与“反转链表”类似,都是断开原来的指针,再指向新的节点;不同的是迭代遍历的时候,每迭代一次,前进两个节点;而且本题需要操作的指针数目也不一样(共三个)。😝
2. 解体思路
【本题的考点】
数据结构:单链表
算法:迭代或者递归
同上一篇文章一样,本题同样提供了两种思路:迭代和递归。我们先说一下迭代。
2.1. 迭代
迭代方法与人脑的思维最接近,所以最容易想到:
- 通过迭代遍历链表(每次迭代,前进两个节点)
- 断开三个节点的指针,指向新的节点。
再多的语言,也不如图示生动活泼,博主爆肝🥴画了迭代示意图,相信可以帮助你快速理解解题思路:
结合上图,很容易得到解题思路:
- 初始化:新增一个虚拟节点并指向head;当前节点指向该虚拟节点;开始每两个节点为一组的迭代:
- 第一步:cur.next 指向当前组的第二个节点(2)
- 第二步:cur.next.next 指向当前组的第一个节点(1)
- 第三步:cur.next.next.next 指向下一组的第一个节点(3)
- 当前节点 cur 往后移两个节点(指向1)
到此,你可能会问了,为什么要使用虚拟节点呢?
因为头节点没有指针指向它,使用虚拟节点指向头节点,会方便很多,要不然每次针对头结点还要单独处理。
2.2. 递归
其实,等你熟悉了递归,你会发现,递归也是一种迭代,只不过是通过“自己调用自己”的方法来迭代。因此,根据上文中迭代的代码,就不难写出递归代码了。
3. 代码实现
3.1. python
单向链表节点类:
class LinkNode:
"""链表节点"""
def __init__(self, val=None, next=None):
self.val = val
self.next = next
3.1.1. 迭代
def swap_paris(head: LinkNode) -> LinkNode:
# 新增虚节点,指向 head
dummy = LinkNode(0)
dummy.next = head
# cur 指向需节点
cur = dummy
# 开始迭代
# 1. 当 cur.next = None 时,说明已经遍历到最后一个节点,迭代可以结束了
# 2. 当 cur.next = None 时,说明链表的节点数是奇数,还剩最后一个节点,无法交换了,迭代也可以结束了
while cur.next and cur.next.next:
cur.next, cur.next.next, cur.next.next.next = cur.next.next, cur.next, cur.next.next.next
cur = cur.next.next # cur 向后移动两个节点,准备下一轮迭代
return dummy.next
如果你刚接触 python ,可能难以理解以上 cur.next, cur.next.next, cur.next.next.next = cur.next.next, cur.next, cur.next.next.next
代码,实际上这是python语言的特性——一次性赋值,即把等号右边的值全部都保存了后再一次性赋值给等号左边的变量。
为了方便你理解,我附上了传统数据交换的写法(下文中 java、scala 语言就是这样写的):
# temp1 保存当前组的第一个节点
temp1 = cur.next
# temp2 保存下一组的第一个节点
temp2 = cur.next.next.next
# 开始通过临时变量交换节点
cur.next = cur.next.next
cur.next.next = temp1
cur.next.next = temp2
LeetCode运行结果
3.1.2 递归
def swap_paris(head: LinkNode) -> LinkNode:
# 新增虚节点,指向 head
dummy = LinkNode(0)
dummy.next = head
# cur 指向虚节点
cur = dummy
# 通过递归方法来迭代
return swap_pairs_recursion(cur, dummy)
def swap_pairs_recursion(cur: LinkNode, dummy: LinkNode) -> LinkNode:
"""递归方法"""
# 递归终止条件
if cur is None or cur.next.next is None:
return dummy.next
# 数据交换、更新cur(跟上一节中迭代中的方法是一摸一样的)
cur.next, cur.next.next, cur.next.next.next = cur.next.next, cur.next, cur.next.next.next
cur = cur.next.next
# 通过“自己调用自己”实现迭代
return swap_pairs_recursion(cur, dummy)
注意,LeetCode 上的的函数是放在 Class 中的,所以需要加上 self;另外,LeetCode上的节点类为 ListNode :
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy = ListNode(0)
dummy.next = head
cur = dummy
return self.swap_pairs_recursion(cur, dummy)
def swap_pairs_recursion(self, cur: ListNode, dummy: ListNode) -> ListNode:
if cur is None or cur.next is None or cur.next.next is None:
return dummy.next
cur.next, cur.next.next, cur.next.next.next = cur.next.next, cur.next, cur.next.next.next
cur = cur.next.next
return self.swap_pairs_recursion(cur, dummy)
3.2. java
单向链表节点类:
public class LinkNode {
public int val;
public LinkNode next;
LinkNode() {}
public LinkNode(int val) { this.val = val; }
public LinkNode(int val, LinkNode next) { this.val = val; this.next = next;}
}
3.2.1. 迭代
// 新增虚节点,指向 head
LinkNode dummy= new LinkNode(0);
dummy.next = head;
// dummy赋给 cur
LinkNode cur = dummy;
// 开始迭代
// 1. 当 cur.next = None 时,说明已经遍历到最后一个节点,迭代可以结束了
// 2. 当 cur.next = None 时,说明链表的节点数是奇数,还剩最后一个节点,无法交换了,迭代也可以结束了
while (cur.next != null && cur.next.next != null) {
// 保存临时变量
LinkNode temp1 = cur.next;
LinkNode temp2 = cur.next.next.next;
// 交换节点
cur.next = cur.next.next;
cur.next.next = temp1;
cur.next.next.next = temp2;
// 更新 cur
cur = cur.next.next;
}
return dummy.next;
LeetCode运行结果
3.2.2. 递归
public static LinkNode swapPairs(LinkNode head) {
// 新增虚节点,指向 head
LinkNode dummy = new LinkNode(0);
dummy.next = head;
// dummy赋给 cur
LinkNode cur = dummy;
// 通过递归方法来迭代
return swapPairsRecursion(cur, dummy);
}
public static LinkNode swapPairsRecursion(LinkNode cur, LinkNode dummy) {
// 递归结束条件
if (cur == null || cur.next == null || cur.next.next == null) {
return dummy.next;
}
// 递归公式
LinkNode temp1 = cur.next;
LinkNode temp2 = cur.next.next.next;
cur.next = cur.next.next;
cur.next.next = temp1;
cur.next.next.next = temp2;
// 更新 cur
cur = cur.next.next;
// 通过“自己调用自己”实现迭代
return swapPairsRecursion(cur, dummy);
}
3.3. scala
单向链表节点类:
class LinkNode(_value: Int = 0, _next: LinkNode = null) {
var next: LinkNode = _next
var value: Int = _value
}
3.3.1. 迭代
// 新增虚节点,指向 head
val dumpy = new LinkNode(0)
dumpy.next = head
// dumpy 赋给 cur
var cur = dumpy
// 开始迭代
// 1. 当 cur.next = None 时,说明已经遍历到最后一个节点,迭代可以结束了
// 2. 当 cur.next = None 时,说明链表的节点数是奇数,还剩最后一个节点,无法交换了,迭代也可以结束了
while (cur.next != null && cur.next.next != null) {
// 保存临时变量
val temp1 = cur.next
val temp2 = cur.next.next.next
// 交换节点
cur.next = cur.next.next
cur.next.next = temp1
cur.next.next.next = temp2
// 更新 cur
cur = cur.next.next
}
// 返回新链表的head
dumpy.next
LeetCode运行结果
3.3.2 递归
def swapPairs(head: LinkNode): LinkNode = {
// 新增虚节点,指向 head
val dumpy = new LinkNode(0)
dumpy.next = head
// dumpy 赋给 cur
var cur = dumpy
// 递归
swapPairsRecursion(cur, dumpy)
}
def swapPairsRecursion(cur: LinkNode, dummy: LinkNode): LinkNode = {
// 递归终止条件
if (cur == null || cur.next == null || cur.next.next == null) {
return dummy.next
}
var curVar = cur
// 递推公式
val temp1 = cur.next
val temp2 = cur.next.next.next
cur.next = cur.next.next
cur.next.next = temp1
cur.next.next.next = temp2
curVar = cur.next.next
swapPairsRecursion(curVar, dummy)
}
4. 总结
通过【链表系列01】和【链表系列02】的学习,相信你已经比较了解单链表这种数据结构了,并且熟悉了单链表相关问题的解题方法,即迭代或者递归。
递归对于初学者来说,虽然比较烧脑、难以理解,但是一旦你熟悉了这种特殊的机器式思维方式,你就会觉得也不过如此嘛!事实上,你会发现,能通过迭代来实现的,也基本上能通过递归实现,无非就是通过“自己调用自己”的方式来实现迭代罢了。
小提示:文中的代码一定要自己敲一遍哦!敲得越多,了解越透彻,掌握越牢固!😎 如果有更好的解题方法,也欢迎您留言。
怎么样?是不是对数据结构和算法有信心了呢?
单向链表就到此为此了,下面一篇文章将讲解双向链表,敬请期待!
不需要打赏啦 😊 喜欢我的文章就关注我😻、点赞👍、收藏⭐吧!谢谢 🤞
如果有问题❓,直接留言就好啦,我会第一时间回复!👨🎓