本文首发于公众号: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
是当前节点,pre
是cur
的前一个结点,那么我们要翻转这两个指针顺序就直接把cur.next
指向pre
即可,即cur.next = pre
由于我们要不断往后移动,重复这个指针操作,所以我们在修改cur
的指针之前,还需要保留next = cur.next
,然后反转之后,我们把pre
更新为cur
,cur
更新为next
初始cur
是head
,那么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
总结
通过讨论四道题目,我们得到了翻转链表的几种操作:
- 翻转一条链表
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
- 翻转链表的前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
- 翻转链表的[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/