【Leetcode】24-两两交换链表中的节点

题目简述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

测试案例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]

数据结构:

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

解法一(迭代)

主要思路

根据数据结构的next属性(指向下一个节点)。交换节点,要求不修改节点的val属性,则只能考虑交换节点的next属性。每次交换相邻节点,我们只需关注 待交换的两个相邻节点这两个相邻节点的前一个节点(称之为前置节点,需要更新其指向的next节点,否则交换后的两个节点会与前面的链表断开)
考虑需要处理的场景(即设计测试场景)有:
(1)头节点与相邻节点的交换(不需考虑前置节点)
(2)链表中某节点与相邻节点的交换(需要考虑前置节点,更改其next属性)
(3)链表节点个数为奇数,即交换两相邻节点后只有单个节点(无需继续交换;包括链表本身只有一个节点的场景,无需交换)
(4)链表节点个数为偶数,即交换两相邻节点后没有其他节点了(无需继续交换)
(5)链表为空(无需交换)
因此,我们使用三个指针r、p、q分别指向待交换的两个相邻节点及前置节点,整体流程分为两大步骤:
1、交换两相邻节点p、q,并更新前置节点(如有)r链接的next节点
2、移动指针,按照r、p、q的顺序指向后续待更新的前置节点和待交换的两个相邻节点
需要注意,返回结果为新表头head,应该在交换开始之前指向预期交换后的新表头节点

代码示例


class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        newhead = head.next
        p = head
        q = p.next
        while q:
        	# 交换两相邻节点,更新前置节点链接的下一个节点
            if p == head: # 如果是头节点,则不必考虑前置节点,交换两相邻节点即可
                p.next = q.next
                q.next = p 
            else:
                r.next = q
                p.next = q.next
                q.next = p 
            # 移动指针
            r = p # 定位前置节点
            p = p.next 
            if not p or not p.next: # 相邻两节点交换后,如果后续无节点或仅有一个节点
                break
            else: # 如果有续至少有2个节点,则继续按照r、p、q的顺序指向前置节点和待交换的两个相邻节点
                q = p.next
        return newhead 

算法复杂度分析

时间复杂度 O ( n ) O(n) O(n) 算法仅需一次迭代链表,时间消耗取决于链表长度
空间复杂度 O ( 1 ) O(1) O(1) 仅使用3个指针,空间消耗为常数项

解法二(递归)

主要思路

为什么这道题可以考虑递归方法呢?

假设待处理链表的节点数量大于2,如该例:
head/node1 ——> node2 ——> node3 ——> node4 ——> node5 ——> ……

我们将两两交换的节点看作一组节点,那么我们较容易想到的方法是:
1、对第一组节点(表头节点head/node1及其下一个节点node2)进行交换:newhead/node2 ——> node1 ——> node3 ——> ……
注意:新的表头节点为node2了,原链表的表头节点成为了新链表的第二个节点,原链表的第二个节点成为了新链表的头节点;
2、再对第二组节点(后面的node3和node4)进行交换:
node4 ——> node3 ——> node5——> ……
3、然后更新第一组节点与第二组节点之间的指向关系,将完成交换后的第一组节点中的第二个节点node1(即原来的第一组节点中的第一个节点)的next指向,由原来的第二组节点中的第一个节点node3改为完成交换后的第二组节点中的第一个节点node4。

上述方法如果直接用代码实现,其实就是迭代法。完成交换完后的每组节点中的第二个节点就是迭代法中所说的前置节点。但还有更简洁的代码实现方式。

容易发现2和1其实是重复的操作。操作2中的原链表( node3 ——> node4 ——> node5 ——> ……)其实是前一个节点的子链表。那么node3就是子链表的原来的头节点,node4是子链表的原来的头节点的下一个节点;对应的,node3也是子链表的新的头节点的下一个节点,node4是子链表的新的头节点。归纳一下,每组节点都是如此,完成交换后 [ 新头节点——>原头节点] ;每组节点及其后面待处理的节点都是前一组节点的子链表。(这是递归的关键,理解内层嵌套结构)
而且如果先完成操作2(即已得到 “node4 ——> node3 ——>……”,这也是递归的关键,内层已有结果,返回给外层,然后继续执行相同操作)再执行操作1的话,我们无需关注每组节点中两个节点交换前后的顺序,只需将前一组节点的原头节点的next指向下一组节点的新头节点
层层嵌套,内层返回结果给外层后再按照同理处理,即是递归。在最内层,递归会停止而返回。本问题中,递归停止的条件是链表没有节点或只有一个节点。

由此,递归算法可以实现:

代码示例

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        newhead = head.next
        head.next = self.swapPairs(newhead.next) 
        newhead.next = head
        return newhead

算法复杂度分析

时间复杂度 O ( n ) O(n) O(n) 对每个节点进行一次处理,因此取决于链表的长度n
空间复杂度 O ( n ) O(n) O(n) 由于使用了递归,使用到的栈空间的深度就是链表的长度,因此空间消耗为取决于n

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值