简介
题目链接 Leetcode 25. Reverse Nodes in k-Group
翻转LinkedList,以每K个节点为一组翻转,不够K的节点的话就保持不变。
作为链表的难度天花板,这道题依然不是很难。建议看解法之前先练习本题的简单版本LeetCode 206. Reverse Linked List。
注意:文章中一切注解皆为Python代码
理解题目
题目非常简单。给定一个链表,每K的节点翻转一次,最后一段不足K个节点就保持不变。这题有两个难点:
- 如何做到每K个节点翻转一次
- 如何处理边界情况,如开头段,结尾段
一旦解决这两个问题,这个题目就是一个Easy题。
做过LeetCode 206的人可能是用while
循环实现的翻转,如果把while
循环换成K
次的for
循环,再返回相应的起点,终点和下一个未访问节点,就可以实现每K
个节点一次的循环,并且获得操作剩余链表最重要的信息。第一个问题也迎刃而解。
再看第二个问题。我们可以把开头和结尾段分开讨论。
假设一个链表再反转的过程中会有三段,开头段(最终会返回这段的开头),中间段,结尾段(有可能不被翻转)。在这种假设下,开头段和中间段的唯一区别就在于,我们需要返回开头段的首节点;因此,我们把开头段单独拿出来翻转,最终返回的时候提取这段的首节点就好。
结尾段的处理稍微复杂,但也不难。我们算出链表的总长(设为n
),再看n % k == 0
是否成立,如果成立则结尾段与中间段没有任何区别;如果不成立我们总共可以翻转的节点数为n - n%k
个(因为最后一段太短不可以翻转)。
一种特殊情况是n < k
,这种情况压根不用翻转,直接返回head
就行。
代码实现
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
def reverse(node, k): # 翻转链表中的K的节点
prev, cur = None, node
for i in range(k):
cur.next, prev, cur = prev, cur, cur.next # 这一行等同于一下被注释的四行
#tmp = cur.next
#cur.next = prev
#prev = cur
#cur = tmp
start, stop, nxt = prev, node, cur # 为了便于理解这里重命名一下
return start, stop, nxt # 返回翻转后链表的起点,终点和下一个未访问节点
n, cur = 0, head
while cur: cur, n = cur.next, n+1 # 计算链表的总长度
if n < k: return head # 如果总长小于K,直接返回首节点,不需要翻转
head, prev_stop, nxt = reverse(head, k) # 单独处理“开头段”
for i in range(k, n-n%k, k): # 处理“中间段”
start, stop, nxt = reverse(nxt, k)
prev_stop.next = start # 用上一段的终点连接当前段的起点
prev_stop = stop # 更新终点
prev_stop.next = nxt # 用终点连接未翻转的“结尾段”
return head # 返回开头段的起点
解题后的思考
尽管标注为Hard
,但是由于链表题整体难度不高,这题稍作拆解也是可以比较轻松解决的。
链表翻转是一个超级基础的技能,不会的话建议背下来,或者多练习几遍熟能生巧;面试过程中,链表翻转和树的遍历一样,应该是提笔就写不假思索那种级别的技能。
有的Hard
题难在算法,数据结构闻所未闻,这题只需要多想想,大事化小,逐个击破就好;这题也可以说是Hard
题之所以Hard
的一个典型——很多Easy
或者Medium
题目的叠加。