链表中的节点每k个一组翻转

描述

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。

数据范围:  0≤n≤2000 0≤n≤2000 , 1≤k≤20001≤k≤2000 ,链表中每个元素都满足 0≤val≤10000≤val≤1000
要求空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

例如:

给定的链表是 1→2→3→4→51→2→3→4→5

对于 k=2k=2 , 你应该返回 2→1→4→3→52→1→4→3→5

对于 k=3k=3 , 你应该返回 3→2→1→4→53→2→1→4→5

解题思路:

  1. 确认是否有足够的节点做一次 k-个的翻转
  • 代码用变量 tail 从 head 开始向后移动 k 步(循环 i 从 0 到 k-1,每次 tail = tail.next)。
  • 目的:检查当前段是否至少有 k 个节点;同时移动后的 tail 将指向“当前段之后的第一个节点”(也就是下一段的 head)。
  • 如果在这 k 次移动中遇到 tail == null,说明剩余节点不足 k 个,函数直接返回 head(不做反转,保持原链表不变)。
  • 直观理解:先看够不够翻。如果不够,就把剩下的直接连上,不动。
  1. 局部翻转当前这 k 个节点(head 到 tail 之间)
  • 设 pre = null,cur = head。循环条件是 while (cur != tail),也就是把从 head 开始直到 tail 前的所有节点反转。
  • 在循环里:
    • 保存 cur.next 到临时变量 temp(因为接下来会改 cur.next),
    • 把 cur.next 指向 pre(完成一步反转),
    • 然后把 pre 移到 cur,cur 移到 temp,继续下一步。
  • 循环结束时:
    • pre 指向已经反转好的这一段的新头(原来的 head 现在是段的尾),
    • cur == tail(指向下一段的起点或 null)。
  1. 连接当前反转后的段与后续处理结果(递归)
  • 现在当前段已经反转完,原来的 head 节点变成了这段的尾节点。要把它的 next 指向后面处理好的链表。
  • 代码用 head.next = reverseKGroup(tail, k); 递归调用,把 tail(即下一段的起点)作为新一轮的 head 去处理并返回处理后的头,接着把当前段的尾接到它上面。
  • 最后返回 pre(当前段反转后新的头),作为这次调用的返回值。
  1. 为什么用递归?
  • 递归把问题自然拆成两部分:先处理好当前一段(反转 k 个),然后把当前段连到后面递归解决的结果上。
  • 递归终止条件是:剩余节点不足 k 个(上面第一步检测到 tail == null),直接返回 head 保持不变。
  • 递归形式使得代码简洁,逻辑清晰:反转一段 + 连接下一段的结果。

举个简单例子帮助理解

  • 假设链表 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7,k = 3。
  • 第一段是 1,2,3;tail 移动后指向 4。把 1,2,3 反转成 3->2->1;然后 head(原 1)的 next 指向 reverseKGroup(4,3) 的结果。
  • 递归处理从 4 开始的链表:4,5,6 反转成 6->5->4,然后把 1.next 指向 6->5->4,接着处理剩下 7(不足 3),直接返回 7。
  • 最终合并:3->2->1->6->5->4->7。

时间与空间复杂度(简要)

  • 时间复杂度:O(n)。每个节点被访问、操作常数次。
  • 空间复杂度:O(n/k) 递归栈深度,最坏情况下 O(n)(当 k 很小时)。如果需要常数额外空间,可以用迭代的方法实现,但递归写法更直观。

常见易错点与注意事项

  • 判断 tail 是否为 null 的位置必须在移动之前或在循环里检查,否则会越界。
  • while 循环条件使用 cur != tail 很巧妙:因为 tail 指向“下一段的第一个节点”,这样就能准确只反转当前这 k 个节点。
  • 递归连接时用 head.next(原 head 是段尾)去接递归结果,是关键的一步,容易写错成 pre.next。

总结(一句话)

  • 先检查当前是否有 k 个节点,若足够:把这 k 个节点就地反转,再把反转后的尾(原 head)接到递归处理后的后半链表上;若不足 k 个则返回原链表不变。

import java.util.*;
public class Solution {
    public ListNode reverseKGroup (ListNode head, int k) {
        //找到每次翻转的尾部
        ListNode tail = head;
        //遍历k次到尾部 
        for(int i = 0; i < k; i++){ 
            //如果不足k到了链表尾,直接返回,不翻转
            if(tail == null) 
                return head;
            tail = tail.next; 
        }
        //翻转时需要的前序和当前节点
        ListNode pre = null; 
        ListNode cur = head;
        //在到达当前段尾节点前
        while(cur != tail){ 
            //翻转
            ListNode temp = cur.next; 
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //当前尾指向下一段要翻转的链表
        head.next = reverseKGroup(tail, k); 
        return pre;
    }
}

如果递归难以理解也可采用以下思路:

  • 程序先统计长度,然后按组迭代:找到每组的头尾,调用局部反转函数把该组翻转,再把各组正确连接起来,直到无法再组成完整的一组为止;整体为原地、线性时间的分组反转实现。
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param head ListNode类
     * @param k int整型
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        // write code here
        //当链表为空时直接返回null
        if (head == null || k == 1) {
            return head;
        }
        //遍历链表得出链表的长度
        ListNode h1 = head;
        int count = 0;
        while (h1 != null) {
            count++;
            h1 = h1.next;
        }
        //如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
        if (count < k) {
            return head;
        }

        //count>k
        int tag = 1;
        ListNode start = head;
        ListNode end = head;
        ListNode temp = null;
        ListNode temp1 = new ListNode(0);
        while (tag * k <= count) {

            int i = k - 1;   //
            //将end从start向后k-1个(start到end共k个)
            while (i-- > 0) {
                end = end.next;
            }
            temp = end.next;
            //将start与end之间的链表next进行反转
            reverse(start, end);
            //标记将每k个一组翻转后得到的新链表头节点
            if (tag == 1) {
                h1 = end;
            }
            start.next = temp;
            temp1.next = end;
            temp1 = start;
            //
            start = temp;
            end = temp;
            tag++;
        }
        return h1;
    }



    private  void reverse(ListNode start, ListNode end) {
        ListNode prev = null;
        ListNode curr = start;
        ListNode endNext =
            end.next;  // 反转终止条件(当curr到达endNext时停止)

        while (curr != endNext) {
            ListNode next = curr.next;  // 保存下一个节点
            curr.next = prev;           // 反转指针
            prev = curr;                // 移动prev
            curr = next;                // 移动curr
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值