【链表系列02】两两交换链表中的节点

系列文章目录

【链表系列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】的学习,相信你已经比较了解单链表这种数据结构了,并且熟悉了单链表相关问题的解题方法,即迭代或者递归。

递归对于初学者来说,虽然比较烧脑、难以理解,但是一旦你熟悉了这种特殊的机器式思维方式,你就会觉得也不过如此嘛!事实上,你会发现,能通过迭代来实现的,也基本上能通过递归实现,无非就是通过“自己调用自己”的方式来实现迭代罢了。

小提示:文中的代码一定要自己敲一遍哦!敲得越多,了解越透彻,掌握越牢固!😎 如果有更好的解题方法,也欢迎您留言。

怎么样?是不是对数据结构和算法有信心了呢?

单向链表就到此为此了,下面一篇文章将讲解双向链表,敬请期待!

在这里插入图片描述
不需要打赏啦 😊 喜欢我的文章就关注我😻、点赞👍、收藏⭐吧!谢谢 🤞
如果有问题❓,直接留言就好啦,我会第一时间回复!👨‍🎓

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值