链表翻转专题

本文首发于公众号:code路漫漫,欢迎关注
在这里插入图片描述

概述

在这里插入图片描述

链表结构描述

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

ListNode串起来就能形成最简单的链表了

题目

翻转整条链表:206. 反转链表
翻转链表上[left,right]区间:92. 反转链表 II
对链表上相邻的2个结点进行翻转:24. 两两交换链表中的节点
对链表上相邻的K个结点进行翻转:25. K 个一组翻转链表

文章内容按照题目的顺序展开,首先从迭代翻转的方式入手,然后接下来我们介绍递归翻转的方式,然后我们在递归翻转的代码基础上,解决206. 反转链表206. 反转链表这两道题目,接着我们尝试从翻转代码的思想,解决24. 两两交换链表中的节点25. K 个一组翻转链表这两道题目

定义

链表翻转其实很简单
给定一条链表 1->2->3->4->N
翻转后变为 4->3->2-1->N,其中N表示None
如果指定某个区间,例如[2,4]区间(假定链表首结点下标为1),那么翻转后变为 1->4->3->2->N
如果我们把链表翻转的功能抽取出来,那么我们可以对链表上两两相邻的结点进行翻转,翻转后变为 2->1->4->3->N
如果我们想要翻转相邻的K个数,例如K=3,那么翻转后结果变为3->2->1->4->N

上面四种翻转情况,对应着题目出现的顺序,万变不离其宗,只要我们掌握了基本操作和思想,此类题目就能迎刃而解

基于迭代的翻转

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
    	pass

首先我们考虑边界情况,如果没有结点或者是只有一个结点,那么我们返回head本身即可,因为不需要翻转

        if not head or not head.next:
            return head

然后我们考虑翻转结点的一般操作:
假设cur是当前节点,precur的前一个结点,那么我们要翻转这两个指针顺序就直接把cur.next指向pre即可,即cur.next = pre
由于我们要不断往后移动,重复这个指针操作,所以我们在修改cur的指针之前,还需要保留next = cur.next,然后反转之后,我们把pre更新为curcur更新为next

初始curhead,那么pre直接取None即可,重复上述过程之后,pre刚好是翻转后的头节点,所以我们返回pre
基于上面的分析,可以写出代码

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        pre,cur = None,head # pre -> cur ->next

        while cur:
            next = cur.next
            cur.next = pre  # cur 指向前一个节点
            pre = cur       # pre 为 cur
            cur = next      # cur 往后移动

        # 返回翻转后的链表头
        return pre

基于递归的翻转

迭代的翻转很好理解,我们自然而然想到有没有基于递归的翻转,事实上有的,只不过不太好理解

首先我们假定有一个函数reverse(head),它能够翻转以head为首的单链表,并且返回翻转后的结果
为了更具体的描述,我们以1->2->3->4->N 为例,其中head是1

那么我们调用reverse(head.next)之后,1->reverse(2->3->4->N),得到4->3->2->N这条链表
但是head.next仍然指向2,此时链表结构是
在这里插入图片描述
此时我们要给逆置后的链表添加上1结点,只需要做如下操作

head.next.next = head
head.next = None

在这里插入图片描述
两个操作就能完成,十分美妙

接下来就是一些细节了,我们看代码就好

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        ans = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        
        return ans

如果你对上面的描述有疑惑,或者对递归不够熟悉,可以查看下面两篇文章
递归的一些例子
python递归逆置一条单链表详解

我们把上面的代码抽取出来

	# 翻转一条链表
    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        ans = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        
        return ans

有了基于递归的翻转,我们可以减少很多直接操作指针的工作量,避免出现错误,接下来的讲解我们都以基于递归的翻转为基础,为了减少篇幅,基于迭代的写法会在文末贴出链接

给翻转指定一个区间

接下来我们看指定区间的翻转,也就是 92. 反转链表 II

直接套用reverseList

首先我们要思考,能不能直接套用reverseList这个函数,答案是可以的,给定一个区间[left,right],我们可以把此区间的首尾结点摘取下来,然后传递reverseList这个函数,接着我们把这条链表再拼接回原来的位置

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        # pre | h -> ... -> t | last ...
        # 临时的头节点
        dh = ListNode(None)
        dh.next = head

        # 找到left的前一个结点 pre
        pre = dh
        for i in range(left-1):
            pre = pre.next
        h = pre.next

        t = h
        for i in range(right-left):
            t = t.next
        last = t.next 	# 找到right的后一个节点 last
        t.next = None

		# 翻转[left,right]区间
        self.reverseList(h)

		# 重新拼接
        pre.next = t
        h.next = last
		# 返回答案
        return dh.next

    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        ans = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        
        return ans

改进reversList

不过上面的代码既然用了递归,我们想办法再把它优化一下,假设我们要优化一个功能给定一条链表,我们翻转这条链表的前n个结点,那么我们reverseList只需要做几个改动就能完成这个功能

    def __init__(self):
    	# 后继结点
        self.last = None
        
    def reverseList(self, head: ListNode, n) -> ListNode:
		# 改动1
        if n==1:
        	self.last = head.next	# 改动2 记录
            return head
        
        ans = self.reverseList(head.next)
        head.next.next = head
        head.next = last			# 改动3 指向后继节点
        
        return ans

那么我们只需要找到left结点的前一个结点即可
下面是代码

class Solution:
    def __init__(self):
        self.last = None

    def reverseList(self, head: ListNode, n) -> ListNode:
        # 改动1
        if n == 1:
            self.last = head.next  # 改动2 记录
            return head

        ans = self.reverseList(head.next,n-1)
        head.next.next = head
        head.next = self.last  # 改动3 指向后继节点

        return ans
        
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        dh = ListNode(None)
        dh.next = head
        pre = dh
        for i in range(left-1):
            pre=pre.next
        pre.next = self.reverseList(pre.next,right-left+1)

        return dh.next

当然我们可以借用递归的思想,把找到pre的这个过程省略,变得更加精简

class Solution:
    def __init__(self):
        self.last = None

    def reverseList(self, head: ListNode, n) -> ListNode:
        # 改动1
        if n == 1:
            self.last = head.next  # 改动2 记录
            return head

        ans = self.reverseList(head.next,n-1)
        head.next.next = head
        head.next = self.last  # 改动3 指向后继节点

        return ans
        
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        if left == 1:
            return self.reverseList(head,right-left+1)
        head.next = self.reverseBetween(head.next,left-1,right-1)

        return head

指定翻转的个数 以2为例

以2为例的交换,我们可以用递归来实现,下面的代码实现很像206. 反转链表

class Solution:
    # 两两交换结点,返回交换后的链表
    def swapPairs(self, head: ListNode) -> ListNode:
        # 如果head不存在 返回None
        if not head or not head.next:
            return head
        # 交换后面的n-2个结点
        tail = self.swapPairs(head.next.next)
        # 交换当前结点
        t = head.next
        t.next = head
        head.next = tail    # 将当前结点指向交换后的链表首结点tail
        # 返回首结点
        return t

实际上两两交换就是翻转两个结点,我们可以复用之前的代码,先统计一遍链表长度,为了我们方便移动指针,然后服用reverseList即可


class Solution:
    def __init__(self):
        self.last = None
        self.size = 0

    def reverseList(self, head: ListNode, n) -> ListNode:
        if not head or not head.next:
            return head
        # 改动1
        if n == 1:
            self.last = head.next  # 改动2 记录
            return head

        ans = self.reverseList(head.next,n-1)
        head.next.next = head
        head.next = self.last  # 改动3 指向后继节点

        return ans

    # 两两交换结点,返回交换后的链表
    def swapPairs(self, head: ListNode) -> ListNode:
        cur = head
        if not cur or not cur.next:
            return head
            
        # 提前遍历一遍链表,统计长度
        t = head
        while t:
            self.size += 1
            t = t.next

        dh = ListNode(None)
        dh.next = head
        pre = dh
        
        while pre:
            pre.next = self.reverseList(pre.next,2)
            self.last = None	# 调用self.reverseList之后记得清空self.last,回到初始状态
            self.size -= 2
            if self.size < 2:
                return dh.next
            pre = pre.next.next

        # 返回首结点
        return dh.next

指定翻转的个数 给定K

指定K个相邻结点的翻转,我们只需要改动两个地方即可

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def __init__(self):
        self.last = None
        self.size = 0
    def reverseList(self, head: ListNode, n) -> ListNode:
        if not head or not head.next:
            return head
        if n == 1:
            self.last = head.next 
            return head

        ans = self.reverseList(head.next,n-1)
        head.next.next = head
        head.next = self.last 

        return ans

    # k个一组翻转链表
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        if not head:
            return head
        t = head
        while t:
            self.size += 1
            t = t.next
            
        dh = ListNode(None)
        dh.next = head
        pre = dh
        
        while pre:
            pre.next = self.reverseList(pre.next,k)
            self.last = None
            self.size -= k	# 2改为k
            if self.size<k:	# 2改为k
                return dh.next

            for _ in range(k):
                pre = pre.next
        

总结

通过讨论四道题目,我们得到了翻转链表的几种操作:

  1. 翻转一条链表
    def reverseList(self, head: ListNode) -> ListNode:
        if not head or not head.next:
            return head
        
        ans = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        
        return ans
  1. 翻转链表的前n个结点
    def __init__(self):
    	# 后继结点
        self.last = None
        
    def reverseList(self, head: ListNode, n) -> ListNode:
		# 改动1
        if n==1:
        	self.last = head.next	# 改动2 记录
            return head
        
        ans = self.reverseList(head.next)
        head.next.next = head
        head.next = last			# 改动3 指向后继节点
        
        return ans
  1. 翻转链表的[left,right]区间
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        dh = ListNode(None)
        dh.next = head
        pre = dh
        for i in range(left-1):
            pre=pre.next
        # pre是left的前一个节点
        pre.next = self.reverseList(pre.next,right-left+1)

        return dh.next

调用规则是 pre.next = self.reverList(pre.next, n),如果多次调用,需要调用后设置self.last = None
4. 翻转链表中K个相邻结点

        while pre:
            pre.next = self.reverseList(pre.next,k)
            self.last = None
            self.size -= k	
            if self.size<k:	
                return dh.next

            for _ in range(k):
                pre = pre.next

参考文章

https://labuladong.gitbook.io/algo/shu-ju-jie-gou-xi-lie/shou-ba-shou-shua-lian-biao-ti-mu-xun-lian-di-gui-si-wei/di-gui-fan-zhuan-lian-biao-de-yi-bu-fen

https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/liang-liang-jiao-huan-lian-biao-zhong-de-jie-di-91/

https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/k-ge-yi-zu-fan-zhuan-lian-biao-by-leetcode-solutio/

猜你喜欢

转载自blog.csdn.net/hhmy77/article/details/114973497
今日推荐