在数据结构与算法的学习和刷题的过程中,链表作为一种重要的线性数据结构,常常会出现在各类面试与竞赛题目里,而反转链表这一经典题目更是频繁亮相,极具代表性。
这篇文章是我刷完灵神b站课程【反转链表】www.bilibili.com/video/BV1sd… 所作的一些总结
链表的基本概念
将火车类比链表,链表的第一个节点是头节点,每一个节点都包含节点值val(value),指向下一个节点的指针next,最后一个节点指向空
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
反转链表
反转后的链表是这样的 头节点指向空,其他的节点指向它的上一个节点
实现反转链表,我们就可以遍历到每一个节点,将它的next指针指向上一个节点,就需要用变量cur(current)表示当前遍历到的节点,用pre(previous)表示上一个节点,对于链表的第一个节点来说它的上一个节点是空,所以previous为空
模拟下 pre初始为空,cur初始为头节点,while cur,将cur.next=pre pre=cur 进入下一个循环,进入不了,因为cur.next已经被改变了,无法将cur定位到下一个节点,这个时候就要用一个临时变量nxt来记录cur.next,使循环能正常运行,如图
代码实现:
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
pre=None
cur=head
while cur:
nxt=cur.next
cur.next=pre
pre=cur
cur=nxt
return pre
用递归来写这个代码
终止条件:如果传入节点为空(空链表),或者传入节点下一个节点为空(只有一个节点的链表不反转以后等于本身)
操作,将当前节点(cur)的下一个节点(cur.next)的next指针(cur.next.next)指向当前节点,将当前节点(cur)与当前节点下一个节点的指针断开(cur.next=None)避免形成环。
调用,将当前节点(cur)的下一个节点(cur.next)传入函数,返回新的cur,调用函数要在操作之前,因为在操作的时候,cur.next会变,跟上面nxt变量操作是一样的
代码实现
class Solution:
def reverseList(self, cur: Optional[ListNode]) -> Optional[ListNode]:
if cur is None or cur.next is None:
return cur
new_cur = self.reverseList(cur.next)
cur.next.next = cur
cur.next = None
return new_cur
如果要反转的是中间一部分的链表
假设有四个节点的链表,要反转中间两个,将中间两个单拎出来,1指向中间链表的头节点,中间链表的头节点指向4
反转之后也是一样的,1指向中间链表的头节点,中间链表的头节点指向4
1的下一个节点(1.next 也就是2)指向4,1就指向3
我们把中间链表的上一个节点叫做p0,反转链表后,pre就是反转链表的末节点,cur是反转链表的下一个节点 把p0指向pre 将p0.next指向cur
为什么1.next为2,反转链表后不是应该会改变吗?仔细看,反转链表的操作是将2的next指针指向None,并不会改变1与2的连接,所以1.next还是可以定位到2的
但是,还有一个特殊情况要考虑,就是如果从头节点开始反转怎么办?p0定位不了,其实也很简单,在头节点前面放一个哨兵节点(dummy),指向头节点就行了
操作一下
定义一个哨兵节点:dummy=ListNode(next=head)
p0先指向哨兵节点:p0=dummy
将p0定位到要反转链表的上一个节点 for _ in range(left-1): p0=p0.next
套公式反转链表:pre=None,cur=p0.next这里并不是while cur 而是循环要反转链表的长度 for _ in range(right-left+1):然后就是反转链表的操作
将p0.next=pre p0.next.next=cur
返回新链表的头节点 如果要反转链表在中间,则返回head就行,如果在开头呢?就返回要反转链表的末节点,这边用到哨兵节点的好处就是,哨兵节点指向链表的头节点,无论是在要反转链表在开头还是不在开头都指向链表的头节点。所以直接返回dummy.next就行了
代码实现
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
dummy=ListNode(next=head)
p0=dummy
for _ in range(left-1):
p0=p0.next
pre=None
cur=p0.next
for _ in range(right-left+1):
nxt=cur.next
cur.next=pre
pre=cur
cur=nxt
p0.next.next=cur
p0.next=pre
return dummy.next
一个小方法:如果头节点会变,用哨兵节点能快速找到头节点
k个一组翻转链表
每逢k个反转一次 考虑的第一个问题,就是如果链表长度不够了怎么办,这道题要知道链表的长度(n) 知道了长度,就是循环,每次n-=k 当n<k时跳出循环,然后每一次循环就是重复上一题的操作
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
cur=head
n=0
while cur:
n+=1
cur=cur.next
dummy=ListNode(next=head)
p0=dummy
while n>=k:
n-=k
pre=None
cur=p0.next
for _ in range(k):
nxt=cur.next
cur.next=pre
pre=cur
cur=nxt
nxt=p0.next
p0.next.next=cur
p0.next=pre
p0=nxt
return dummy.next
这里要注意一点的就是,要更新p0的位置,用nxt将p0.next记录下来。
以上是我的一些总结和理解,如果你觉得有用点个赞吧,如果有什么不足请多指教(抱拳)