常见编程模式之就地反转链表
6. 就地反转链表(In-place Reversal of a LinkedList)
基本原理及应用场景
在很多问题中,我们需要对一个链表中的节点连接进行反转,且通常需要原地进行,即不能使用额外的存储空间。这时我们可以使用就地反转链表模式,该模式本质上是一种迭代解法,流程如下图所示。首先设置一个变量 current
指向链表头部,以及另一个变量 previous
指向当前处理节点的前一个节点。下一步我们需要将当前节点 current
进行反转,将其指向前一个节点 previous
,然后将 previous
指向当前节点,再将 current
移动到下一个节点,开始迭代直至遍历完成。
经典例题
206. 反转链表(Easy)
反转一个单链表。
「示例」:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
这道题可以采用就地反转链表模式,即迭代方法,参考上面的讲解,代码实现如下:
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
curr, pre = head, None
while curr:
temp_next = curr.next
curr.next = pre
pre = curr
curr = temp_next
return pre
当然这道题也可以采用递归实现,但是使用了隐式的栈空间,且不是非常好理解:
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
p = self.reverseList(head.next) # 一直递归到最后一个元素之前,返回p为head.next(即最后一个节点)
head.next.next = head # 在当前递归中将head.next的下一个节点反转为head
head.next = None # 清除当前节点的下一个节点,防止循环(实际上只对最后一个节点有用,前面的会自动修改)
return p
递归的原理可以通过下图理解(来自 Leetcode-王尼玛):
92. 反转链表 II(Medium)
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
1 ≤ m ≤ n ≤ 链表长度。
「示例:」
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
这道题可以采用就地反转链表模式,由于只翻转部分位置,所以需要设置两个额外的节点进行标记,翻转完成后将翻转部分和未翻转的部分进行拼接。具体的代码实现如下:
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
if not head: return None
cur, prev = head, None
while m > 1: # 将cur移到开始反转的位置,prev移到其前一个节点
prev = cur
cur = cur.next
m, n = m - 1, n - 1 # 注意对n也进行处理,相当于n=n-m+1
tail, con = cur, prev # 设置两个额外节点,用于之后拼接链表
while n: # 执行就地反转链表模式
temp_next = cur.next
cur.next = prev
prev = cur
cur = temp_next
n -= 1
# 拼接头部
if con:
con.next = prev
else:
head = prev
# 拼接尾部(实际上最开始的prev被丢弃了)
tail.next = cur
return head
本题也可以采用递归方法进行求解,我们先定义一个反转前 n 个节点的子函数,然后递归至起始位置开始反转即可:
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
def reverseN(head, n):
if n == 1:
return head
p = reverseN(head.next, n - 1)
successor = head.next.next # 始终指向反转节点的后一个节点
head.next.next = head # 反转链表
head.next = successor # 将最后一个节点指向后面未反转的部分
return p
if m == 1: return reverseN(head, n)
head.next = self.reverseBetween(head.next, m - 1, n - 1)
return head
25. K 个一组反转链表(Hard)
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。 如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
「示例」:
给你这个链表:1->2->3->4->5
:
- 当 k = 2 时,应当返回:
2->1->4->3->5
- 当 k = 3 时,应当返回:
3->2->1->4->5
这道题同样可以使用就地反转链表模式,我们需要构建一个反转子链表的函数,然后遍历目标链表,达到 k 个则进行反转,并将其拼接回主链表中。这里的关键技巧是通过哑结点来保证每次相同的遍历次数,以及方便最后的返回。具体的代码实现如下:
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
def reverse(head, tail):
# 翻转一个子链表,并且返回新的头与尾
prev = tail.next # 指向尾节点的下一个节点
curr = head
while prev != tail: # 不能用tail.next判断,其指向已经改变
temp_next = curr.next
curr.next = prev
prev = curr
curr = temp_next
return tail, head
dummy = ListNode(0) # 设置一个哑结点,方便进行k次遍历(作为pre)以及最终的返回
dummy.next = head
pre = dummy # pre为子链表的前一个节点,用于将子链表接回原链表
while head:
tail = pre
# 查看剩余的长度是否大于等于k
for i in range(k): # 从起始节点的前一个结点开始,刚好k次到尾部
tail = tail.next
if not tail:
return dummy.next # 不满足直接返回
temp_next = tail.next # 记录子链表的下一个节点
head, tail = reverse(head, tail) # 反转子链表
# 把子链表接回原链表,并设置新的pre与head
pre.next = head
tail.next = temp_next
pre = tail
head = tail.next
return dummy.next
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 这就是你日日夜夜想要的docker!!!---------TLS加密远程连接Docker
- 【Flink】基于 Flink 的流式数据实时去重
- 从零开始安装穿透式检索
- 使用kind搭建kubernetes
- 如何设计一个牛逼的API接口
- 猿实战17——实现你未必知晓的运费模板
- 这 5 个 VSCode 扩展提高你的开发效率
- 猿实战18——商品发布之类目选择
- 树莓派基础实验1:双色LED灯实验
- 【机器学习基础】机器学习中类别变量的编码方法总结
- 树莓派基础实验2:RGB-LED实验
- 【Python基础】Python画王者荣耀英雄能力雷达图
- 树莓派基础实验4:继电器实验
- 树莓派基础实验5:激光传感器实验
- 树莓派基础实验6:轻触开关按键实验