# !/usr/bin/env python3.7
# -*- coding: utf-8 -*-
# @Time : 2023/6/22 22:01
# @Author : Aulicz
# @File : 25reverseKGroup.py
# @Software: PyCharm
# @Version:0.01
# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
from typing import Optional
# 给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
#
# k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
#
# 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换
#
# 提示:
# 链表中的节点数目为 n
# 1 <= k <= n <= 5000
# 0 <= Node.val <= 1000
#
#
# 进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?
# 基础的翻转链表,可以通过栈然后构建新链表完成,但是额外内存是O(n)
# 原地重排:两两进行翻转
class Solution:
# 此题的解法解决了对整个链表k倒转问题,题目要求是不足k个的不进行倒转
def reverseKGroup1(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
pre = None
cur = head
counter = 0
group_head = head
if head is None:
return None
if k == 1:
return head
# 第一组的最后一个是新的头节点,需要特殊处理
is_first_group = True
res_head = None
# 还需要将上一组的最后一个和下一组第一个拼接
last_group_end = None
while cur:
if counter == k:
# 完成一组,上一组第一个节点指向新节点
# 这里是个临时的指向,如k=2 [1,2,3,4,5],完成12的翻转后,将1指向3
# 实际上应当在cur=5时,将1指向4
group_head.next = cur
# 如果是第一组,此时的pre就是新的头节点
if is_first_group:
res_head = pre
is_first_group = False
# 以k=2 [1,2,3,4,5]为例,此时pre是2 group_head是1
if last_group_end is not None:
# 从第三组开始,你需要把上上一组现在的末尾,指向的上一组第一个节点
last_group_end.next = pre
# 以k=2 [1,2,3,4,5]为例,此时last_group_end是1 pre是4
last_group_end = group_head # 保存上一组的group_head
group_head = cur # 更新group_head
counter = 0
elif counter == 0:
# 第一个不需要与上一组翻转,所以跳过交换
# 为啥不直接在前面if k 跳到1?因为不保证cur!=None需要重复判断
pre = cur
cur = cur.next
counter += 1
else:
# 交换两个节点
after = cur.next
cur.next = pre
pre = cur
cur = after
counter += 1
group_head.next = None
if res_head:
# 至少有一组
last_group_end.next = pre
return res_head
else:
# 说明链表长度小于k,直接返回pre
return pre
# 开始我试着用一个detect_node在每一组前确认一次
# 实际上这样复杂度也是O(2n),所以直接提前求链表长度显然更简单
def reverseKGroup2(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# 在之前的解法中,使用res_head存翻转后头节点位置
# group_head存翻转前第一个节点,last_group_end存上一组最后一个节点
# 非常繁琐
# 考虑中间翻转某几位的情况
# 如[1,2,3,4,5]翻转2,3,4
# 开始时pre指向1,cur指向2
# 翻转后 pre指向4,cur指向5
# 需要将1指向4,2指向5
# 边界情况,前面没有节点时,可以采用哨兵
# 如此一来,只需要记得上一个pre 和cur即可
# 但是这么做也有问题,当k = 2为例,以-1代表哨兵
# 第一次会将[-1,1]指向[2,3]这是没问题的
# 第二次会将[2,3]指向[4,5],原本2->1会丢失
# 所有需要有两次以上翻转的例子都会出问题,说明算法有问题
if head is None:
return None
n = 0
cur = head
# 统计节点个数
while cur:
n += 1
cur = cur.next
last_pre = dummy = ListNode(-1, head)
last_cur = head
pre = None
cur = head
while n >= k:
n -= k
for _ in range(k):
# 调换两个节点的顺序
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
# print([last_pre.val, last_cur.val, pre.val, cur.val])
last_pre.next = pre
last_cur.next = cur
last_pre = pre
last_cur = cur
# 剩下的什么也不做即可
return dummy.next
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# 正缺的连接次序是
# 第一次[-1,1]指向[2,3]
# 第二次[1,3]指向[4,5]
# 如果延长前面的例子,第三次[3,5]指向[6,7]
# 显然,第一个指针起点不是上一个pre,而是上上个cur,改成如下顺利通过了
# 介绍一种大佬的写法
# p0的指向顺序依次是,第一次-1,第二次1,第三次3
# 最离谱的是,最终结果时,每组第一个,会指向下一组最后一个(以原顺序为参考系)
# 但是中间态,会将每组第一个,指向下一组第一个
# 第一次时-1下一个是1;第二次时1下一个是3;第三次时,3下一个是5
# 只能说可以直接想到这么找起点的是神人
# nxt = p0.next
# nxt.next = cur
# p0.next = pre
# p0 = nxt
if head is None:
return None
n = 0
cur = head
# 统计节点个数
while cur:
n += 1
cur = cur.next
last_last_cur = dummy = ListNode(-1, head)
last_cur = head
pre = None
cur = head
while n >= k:
n -= k
for _ in range(k):
# 调换两个节点的顺序
nxt = cur.next
cur.next = pre
pre = cur
cur = nxt
# print([last_last_cur.val, last_cur.val, pre.val, cur.val])
last_last_cur.next = pre
last_cur.next = cur
last_last_cur = last_cur
last_cur = cur
# 剩下的什么也不做即可
return dummy.next
solution = Solution()
p1 = ListNode(5)
p2 = ListNode(4, p1)
p1 = ListNode(3, p2)
p2 = ListNode(2, p1)
p1 = ListNode(1, p2)
h = solution.reverseKGroup(p1, 2)
while h:
print(h.val)
h = h.next
leetcode25刷题记录-K个一组翻转
于 2023-06-23 21:38:03 首次发布