leetcode25刷题记录-K个一组翻转

# !/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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值