Java详解LeetCode 热题 100(31):LeetCode 25. K个一组翻转链表(Reverse Nodes in k-Group)详解

1. 题目描述

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
解释:每2个节点一组进行翻转:
原始:1 → 2 → 3 → 4 → 5
翻转:2 → 1 → 4 → 3 → 5

示例 2:

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
解释:每3个节点一组进行翻转:
原始:1 → 2 → 3 → 4 → 5
翻转:3 → 2 → 1 → 4 → 5(最后2个节点不足3个,保持原序)

示例 3:

输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]
解释:k=1时,相当于不翻转

示例 4:

输入:head = [1], k = 1
输出:[1]
解释:单个节点的情况

示例 5:

输入:head = [], k = 2
输出:[]
解释:空链表的情况

提示:

  • 链表中节点的数目在范围 [0, 5000]
  • 0 <= Node.val <= 1000
  • 1 <= k <= 链表长度

进阶要求:

  • 你可以设计一个只使用 O(1) 额外内存空间的算法解决此问题吗?
  • 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

1.1 链表节点定义

/**
 * 单链表节点的定义
 */
public class ListNode {
    int val;           // 节点的值
    ListNode next;     // 指向下一个节点的指针
    
    // 无参构造函数
    ListNode() {}
    
    // 带值的构造函数
    ListNode(int val) { 
        this.val = val; 
    }
    
    // 带值和下一个节点的构造函数
    ListNode(int val, ListNode next) { 
        this.val = val; 
        this.next = next; 
    }
}

2. 理解题目

K个一组翻转链表是链表操作中的经典难题,它是"两两交换链表中的节点"(LeetCode 24)的泛化版本。这道题考查的是对链表操作的深度理解和指针操作的精确控制。

关键概念:

  1. 分组翻转:将链表按照k个节点为一组进行划分
  2. 完整组翻转:只有当一组包含完整的k个节点时才进行翻转
  3. 剩余节点保持原序:不足k个节点的剩余部分保持原有顺序
  4. 实际节点交换:必须通过改变指针连接来实现,不能只修改节点值

2.1 问题可视化

示例 1 详细分析: head = [1,2,3,4,5], k = 2

原始链表:
1 → 2 → 3 → 4 → 5 → null

分组情况:
第1组:[1, 2]  (2个节点,满足k=2,需要翻转)
第2组:[3, 4]  (2个节点,满足k=2,需要翻转)
第3组:[5]     (1个节点,不足k=2,保持原序)

翻转过程:
步骤1:翻转第1组 [1,2] → [2,1]
       结果:2 → 1 → 3 → 4 → 5
       
步骤2:翻转第2组 [3,4] → [4,3]
       结果:2 → 1 → 4 → 3 → 5

最终结果:[2,1,4,3,5]

示例 2 详细分析: head = [1,2,3,4,5], k = 3

原始链表:
1 → 2 → 3 → 4 → 5 → null

分组情况:
第1组:[1, 2, 3]  (3个节点,满足k=3,需要翻转)
第2组:[4, 5]     (2个节点,不足k=3,保持原序)

翻转过程:
步骤1:翻转第1组 [1,2,3] → [3,2,1]
       结果:3 → 2 → 1 → 4 → 5

最终结果:[3,2,1,4,5]

2.2 核心挑战

  1. 精确分组:准确识别每个长度为k的组
  2. 条件翻转:只翻转完整的k个节点组
  3. 链表重连:翻转后正确连接各个组
  4. 边界处理:处理空链表、k=1、链表长度不足k等情况
  5. 指针管理:维护前驱、当前组、后继的指针关系

2.3 思路分析

方法一:迭代法(推荐)

  1. 先计算链表长度,确定需要翻转的组数
  2. 逐组进行翻转操作
  3. 维护前驱节点,处理组间连接

方法二:递归法

  1. 处理当前的k个节点
  2. 递归处理剩余部分
  3. 连接当前组和递归结果

方法三:栈辅助法

  1. 使用栈存储每k个节点
  2. 弹出栈中节点重新连接
  3. 处理剩余不足k个的节点

3. 解法一:迭代法(预计算长度)

3.1 算法思路

这是最容易理解的解法:先遍历一次链表计算总长度,然后根据长度确定需要翻转多少个完整的组。

核心步骤:

  1. 使用哨兵节点简化头节点处理
  2. 计算链表总长度
  3. 计算需要翻转的完整组数:count = length / k
  4. 对每个完整组执行翻转操作
  5. 维护前驱指针,连接各个翻转后的组

3.2 Java代码实现

/**
 * 解法一:迭代法(预计算长度)
 * 时间复杂度:O(n),其中 n 是链表的长度
 * 空间复杂度:O(1)
 */
class Solution1 {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 边界处理
        if (head == null || k <= 1) {
            return head;
        }
        
        // 创建哨兵节点,简化头节点处理
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        // 第一步:计算链表长度
        int length = 0;
        ListNode current = head;
        while (current != null) {
            length++;
            current = current.next;
        }
        
        // 第二步:计算需要翻转的完整组数
        int groupCount = length / k;
        
        // 第三步:逐组翻转
        ListNode prev = dummy;
        ListNode groupHead = prev.next;
        
        for (int i = 0; i < groupCount; i++) {
            // 翻转当前组的k个节点
            prev = reverseGroup(prev, k);
            groupHead = prev.next;
        }
        
        return dummy.next;
    }
    
    /**
     * 翻转prev后面的k个节点
     * @param prev 要翻转组的前驱节点
     * @param k 组大小
     * @return 翻转后该组的最后一个节点(下一组的前驱)
     */
    private ListNode reverseGroup(ListNode prev, int k) {
        // 保存要翻转组的第一个节点,翻转后它将成为该组的最后一个节点
        ListNode groupTail = prev.next;
        ListNode current = prev.next;
        
        // 执行k-1次翻转操作
        for (int i = 0; i < k - 1; i++) {
            ListNode next = current.next;
            current.next = next.next;
            next.next = prev.next;
            prev.next = next;
        }
        
        return groupTail;
    }
}

3.3 详细执行过程演示

/**
 * 带详细调试输出的迭代法实现
 */
public class IterativeMethodDemo {
    public ListNode reverseKGroup(ListNode head, int k) {
        System.out.println("=== 迭代法K个一组翻转链表 ===");
        System.out.println("原始链表: " + printList(head));
        System.out.println("k = " + k);
        
        if (head == null || k <= 1) {
            System.out.println("无需翻转,直接返回");
            return head;
        }
        
        // 创建哨兵节点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        System.out.println("创建哨兵节点: " + printListWithDummy(dummy));
        
        // 计算链表长度
        System.out.println("\n第一步:计算链表长度");
        int length = 0;
        ListNode current = head;
        while (current != null) {
            length++;
            System.out.println("  访问节点值: " + current.val + ",当前长度: " + length);
            current = current.next;
        }
        
        int groupCount = length / k;
        int remainCount = length % k;
        System.out.println("链表总长度: " + length);
        System.out.println("完整组数: " + groupCount + " (每组" + k + "个节点)");
        System.out.println("剩余节点数: " + remainCount + " (保持原序)");
        
        if (groupCount == 0) {
            System.out.println("没有完整的组需要翻转");
            return head;
        }
        
        // 逐组翻转
        System.out.println("\n第二步:逐组翻转");
        ListNode prev = dummy;
        
        for (int i = 0; i < groupCount; i++) {
            System.out.println("\n--- 翻转第" + (i + 1) + "组 ---");
            System.out.println("翻转前: " + printListWithDummy(dummy));
            
            prev = reverseGroupWithDebug(prev, k, i + 1);
            
            System.out.println("翻转后: " + printListWithDummy(dummy));
        }
        
        ListNode result = dummy.next;
        System.out.println("\n最终结果: " + printList(result));
        return result;
    }
    
    private ListNode reverseGroupWithDebug(ListNode prev, int k, int groupIndex) {
        System.out.println("  开始翻转第" + groupIndex + "组的" + k + "个节点");
        
        // 显示当前组的节点
        ListNode temp = prev.next;
        System.out.print("  当前组节点: [");
        for (int i = 0; i < k && temp != null; i++) {
            System.out.print(temp.val);
            if (i < k - 1 && temp.next != null) System.out.print(", ");
            temp = temp.next;
        }
        System.out.println("]");
        
        ListNode groupTail = prev.next;
        ListNode current = prev.next;
        
        // 执行翻转
        for (int i = 0; i < k - 1; i++) {
            ListNode next = current.next;
            current.next = next.next;
            next.next = prev.next;
            prev.next = next;
            
            System.out.println("    翻转操作 " + (i + 1) + ": 将节点" + next.val + "移到组首");
            System.out.println("    当前状态: " + printListWithDummy(findDummy(prev)));
        }
        
        System.out.println("  第" + groupIndex + "组翻转完成,返回新的前驱节点: " + groupTail.val);
        return groupTail;
    }
    
    // 辅助方法:打印链表
    private String printList(ListNode head) {
        if (head == null) return "[]";
        
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        ListNode current = head;
        while (current != null) {
            sb.append(current.val);
            if (current.next != null) {
                sb.append(" → ");
            }
            current = current.next;
        }
        sb.append("]");
        return sb.toString();
    }
    
    // 辅助方法:打印包含哨兵节点的链表
    private String printListWithDummy(ListNode dummy) {
        StringBuilder sb = new StringBuilder();
        sb.append("[哨兵:0] → ");
        if (dummy.next != null) {
            sb.append(printList(dummy.next));
        } else {
            sb.append("null");
        }
        return sb.toString();
    }
    
    // 辅助方法:找到哨兵节点(用于调试)
    private ListNode findDummy(ListNode node) {
        // 简化实现,实际项目中不建议这样做
        ListNode dummy = new ListNode(0);
        dummy.next = node.next;
        return dummy;
    }
}

3.4 执行结果示例

示例:head = [1,2,3,4,5], k = 3

=== 迭代法K个一组翻转链表 ===
原始链表: [1 → 2 → 3 → 4 → 5]
k = 3
创建哨兵节点: [哨兵:0] → [1 → 2 → 3 → 4 → 5]

第一步:计算链表长度
  访问节点值: 1,当前长度: 1
  访问节点值: 2,当前长度: 2
  访问节点值: 3,当前长度: 3
  访问节点值: 4,当前长度: 4
  访问节点值: 5,当前长度: 5
链表总长度: 5
完整组数: 1 (每组3个节点)
剩余节点数: 2 (保持原序)

第二步:逐组翻转

--- 翻转第1组 ---
翻转前: [哨兵:0] → [1 → 2 → 3 → 4 → 5]
  开始翻转第1组的3个节点
  当前组节点: [1, 2, 3]
    翻转操作 1: 将节点2移到组首
    当前状态: [哨兵:0] → [2 → 1 → 3 → 4 → 5]
    翻转操作 2: 将节点3移到组首
    当前状态: [哨兵:0] → [3 → 2 → 1 → 4 → 5]
  第1组翻转完成,返回新的前驱节点: 1
翻转后: [哨兵:0] → [3 → 2 → 1 → 4 → 5]

最终结果: [3 → 2 → 1 → 4 → 5]

3.5 复杂度分析

时间复杂度: O(n)

  • 第一次遍历计算长度:O(n)
  • 翻转操作:每个节点最多被操作常数次,总计O(n)
  • 总时间复杂度:O(n)

空间复杂度: O(1)

  • 只使用了常数级别的额外空间
  • 哨兵节点和几个指针变量占用固定空间

3.6 优缺点分析

优点:

  1. 逻辑清晰:先计算长度,再按组翻转,思路直观
  2. 易于理解:每个步骤都很明确,便于调试
  3. 空间效率高:只使用常数额外空间
  4. 边界处理简单:预先知道组数,避免越界

缺点:

  1. 需要两次遍历:先计算长度,再执行翻转
  2. 代码相对复杂:翻转逻辑需要仔细处理指针关系

4. 解法二:递归法

4.1 算法思路

递归法的核心思想是:处理前k个节点,然后递归处理剩余部分,最后将两部分连接起来。

核心思想:

  1. 递归边界:如果剩余节点不足k个,直接返回头节点
  2. 当前处理:翻转当前的k个节点
  3. 递归调用:递归处理第k+1个节点开始的剩余部分
  4. 连接结果:将翻转的k个节点与递归结果连接

4.2 Java代码实现

/**
 * 解法二:递归法
 * 时间复杂度:O(n)
 * 空间复杂度:O(n/k) - 递归调用栈深度
 */
class Solution2 {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 边界处理
        if (head == null || k <= 1) {
            return head;
        }
        
        // 检查剩余节点是否足够k个
        ListNode current = head;
        for (int i = 0; i < k; i++) {
            if (current == null) {
                // 不足k个节点,直接返回head
                return head;
            }
            current = current.next;
        }
        
        // 翻转前k个节点
        ListNode newHead = reverseFirstK(head, k);
        
        // 递归处理剩余部分,并连接到翻转后的尾部
        // 注意:翻转后,原来的head变成了尾节点
        head.next = reverseKGroup(current, k);
        
        return newHead;
    }
    
    /**
     * 翻转以head开头的前k个节点
     * @param head 要翻转的链表头节点
     * @param k 要翻转的节点数量
     * @return 翻转后的新头节点
     */
    private ListNode reverseFirstK(ListNode head, int k) {
        ListNode prev = null;
        ListNode current = head;
        
        for (int i = 0; i < k; i++) {
            ListNode next = current.next;
            current.next = prev;
            prev = current;
            current = next;
        }
        
        return prev;
    }
}

4.3 递归可视化演示

/**
 * 带递归过程可视化的实现
 */
public class RecursiveMethodDemo {
    private int depth = 0; // 递归深度
    
    public ListNode reverseKGroup(ListNode head, int k) {
        System.out.println("=== 递归法K个一组翻转链表 ===");
        System.out.println("原始链表: " + printList(head));
        System.out.println("k = " + k);
        System.out.println();
        
        ListNode result = reverseKGroupHelper(head, k);
        
        System.out.println("\n最终结果: " + printList(result));
        return result;
    }
    
    private ListNode reverseKGroupHelper(ListNode head, int k) {
        String indent = "  ".repeat(depth);
        System.out.println(indent + "递归层级 " + depth + ":");
        System.out.println(indent + "  输入链表: " + printList(head));
        System.out.println(indent + "  k = " + k);
        
        // 边界处理
        if (head == null || k <= 1) {
            System.out.println(indent + "  边界条件:返回原链表");
            return head;
        }
        
        // 检查是否有足够的k个节点
        ListNode current = head;
        int count = 0;
        for (int i = 0; i < k; i++) {
            if (current == null) {
                System.out.println(indent + "  剩余节点不足" + k + "个,返回原链表");
                return head;
            }
            count++;
            current = current.next;
        }
        
        System.out.println(indent + "  前" + k + "个节点: " + printListRange(head, k));
        System.out.println(indent + "  剩余节点: " + printList(current));
        
        // 翻转前k个节点
        System.out.println(indent + "  翻转前" + k + "个节点...");
        ListNode newHead = reverseFirstKWithDebug(head, k, indent);
        
        System.out.println(indent + "  翻转后的头节点: " + newHead.val);
        System.out.println(indent + "  翻转后的尾节点: " + head.val);
        
        // 递归处理剩余部分
        System.out.println(indent + "  递归处理剩余部分...");
        depth++;
        ListNode recursiveResult = reverseKGroupHelper(current, k);
        depth--;
        
        System.out.println(indent + "  递归返回结果: " + printList(recursiveResult));
        
        // 连接翻转部分和递归结果
        head.next = recursiveResult;
        System.out.println(indent + "  连接后的结果: " + printList(newHead));
        
        return newHead;
    }
    
    private ListNode reverseFirstKWithDebug(ListNode head, int k, String indent) {
        ListNode prev = null;
        ListNode current = head;
        
        System.out.println(indent + "    开始翻转前" + k + "个节点:");
        
        for (int i = 0; i < k; i++) {
            ListNode next = current.next;
            current.next = prev;
            System.out.println(indent + "      步骤" + (i + 1) + ": " + current.val + " → " + 
                             (prev != null ? prev.val : "null"));
            prev = current;
            current = next;
        }
        
        System.out.println(indent + "    翻转完成,新头节点: " + prev.val);
        return prev;
    }
    
    // 辅助方法:打印指定数量的节点
    private String printListRange(ListNode head, int count) {
        if (head == null) return "[]";
        
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        ListNode current = head;
        for (int i = 0; i < count && current != null; i++) {
            sb.append(current.val);
            if (i < count - 1 && current.next != null) {
                sb.append(" → ");
            }
            current = current.next;
        }
        sb.append("]");
        return sb.toString();
    }
    
    // 辅助方法:打印链表
    private String printList(ListNode head) {
        if (head == null) return "null";
        
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        ListNode current = head;
        while (current != null) {
            sb.append(current.val);
            if (current.next != null) {
                sb.append(" → ");
            }
            current = current.next;
        }
        sb.append("]");
        return sb.toString();
    }
}

4.4 递归执行过程分析

递归调用栈的形成过程(输入: [1,2,3,4,5], k=3):

递归层级 0:
  输入链表: [1 → 2 → 3 → 4 → 5]
  k = 3
  前3个节点: [1 → 2 → 3]
  剩余节点: [4 → 5]
  翻转前3个节点...
    开始翻转前3个节点:
      步骤1: 1 → null
      步骤2: 2 → 1
      步骤3: 3 → 2
    翻转完成,新头节点: 3
  翻转后的头节点: 3
  翻转后的尾节点: 1
  递归处理剩余部分...

  递归层级 1:
    输入链表: [4 → 5]
    k = 3
    剩余节点不足3个,返回原链表

  递归返回结果: [4 → 5]
  连接后的结果: [3 → 2 → 1 → 4 → 5]

最终结果: [3 → 2 → 1 → 4 → 5]

4.5 递归法复杂度分析

时间复杂度: O(n)

  • 每个节点被访问常数次
  • 递归深度为O(n/k),但每层处理O(k)个节点

空间复杂度: O(n/k)

  • 递归调用栈的深度为n/k
  • 每层递归使用常数空间

5. 解法三:栈辅助法

5.1 算法思路

使用栈来辅助翻转操作,将每k个节点入栈,然后依次出栈重新连接。

核心步骤:

  1. 遍历链表,每k个节点入栈
  2. 当栈中有k个节点时,依次出栈并重新连接
  3. 处理剩余不足k个的节点

5.2 Java代码实现

import java.util.Stack;

/**
 * 解法三:栈辅助法
 * 时间复杂度:O(n)
 * 空间复杂度:O(k)
 */
class Solution3 {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || k <= 1) {
            return head;
        }
        
        Stack<ListNode> stack = new Stack<>();
        ListNode dummy = new ListNode(0);
        ListNode prev = dummy;
        ListNode current = head;
        
        while (current != null) {
            // 收集k个节点到栈中
            for (int i = 0; i < k && current != null; i++) {
                stack.push(current);
                current = current.next;
            }
            
            // 如果栈中有k个节点,则出栈重新连接
            if (stack.size() == k) {
                while (!stack.isEmpty()) {
                    prev.next = stack.pop();
                    prev = prev.next;
                }
                prev.next = current; // 连接到下一组
            } else {
                // 不足k个节点,保持原有顺序
                prev.next = head;
                // 找到最后k个节点的起始位置
                for (int i = 0; i < stack.size(); i++) {
                    ListNode temp = head;
                    for (int j = 0; j < i; j++) {
                        temp = temp.next;
                    }
                    prev.next = temp;
                    prev = prev.next;
                }
                break;
            }
        }
        
        return dummy.next;
    }
}

5.3 栈法的简化实现

/**
 * 解法三:栈辅助法(简化版)
 * 更清晰的实现逻辑
 */
class Solution3Simplified {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || k <= 1) {
            return head;
        }
        
        // 首先检查总长度
        int length = getLength(head);
        if (length < k) {
            return head;
        }
        
        Stack<ListNode> stack = new Stack<>();
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        ListNode current = head;
        
        while (current != null) {
            // 将k个节点入栈
            for (int i = 0; i < k && current != null; i++) {
                stack.push(current);
                current = current.next;
            }
            
            // 如果栈中恰好有k个节点,则出栈重新连接
            if (stack.size() == k) {
                while (!stack.isEmpty()) {
                    tail.next = stack.pop();
                    tail = tail.next;
                }
                tail.next = null; // 断开与后续节点的连接
            } else {
                // 不足k个节点,将栈中节点按原顺序连接
                ListNode[] nodes = new ListNode[stack.size()];
                for (int i = stack.size() - 1; i >= 0; i--) {
                    nodes[i] = stack.pop();
                }
                for (ListNode node : nodes) {
                    tail.next = node;
                    tail = tail.next;
                }
                tail.next = null;
                break;
            }
        }
        
        return dummy.next;
    }
    
    private int getLength(ListNode head) {
        int length = 0;
        while (head != null) {
            length++;
            head = head.next;
        }
        return length;
    }
}

6. 完整测试用例

6.1 测试框架

import java.util.*;

/**
 * K个一组翻转链表完整测试类
 */
public class ReverseKGroupTest {
    
    /**
     * 创建测试链表的辅助方法
     */
    public static ListNode createList(int[] values) {
        if (values.length == 0) {
            return null;
        }
        
        ListNode head = new ListNode(values[0]);
        ListNode current = head;
        
        for (int i = 1; i < values.length; i++) {
            current.next = new ListNode(values[i]);
            current = current.next;
        }
        
        return head;
    }
    
    /**
     * 将链表转换为数组,便于比较结果
     */
    public static int[] listToArray(ListNode head) {
        List<Integer> result = new ArrayList<>();
        ListNode current = head;
        
        while (current != null) {
            result.add(current.val);
            current = current.next;
        }
        
        return result.stream().mapToInt(i -> i).toArray();
    }
    
    /**
     * 运行所有测试用例
     */
    public static void runAllTests() {
        System.out.println("=== K个一组翻转链表完整测试 ===\n");
        
        // 测试用例
        TestCase[] testCases = {
            new TestCase(new int[]{1, 2, 3, 4, 5}, 2, 
                        new int[]{2, 1, 4, 3, 5}, "示例1:k=2,有剩余"),
            new TestCase(new int[]{1, 2, 3, 4, 5}, 3, 
                        new int[]{3, 2, 1, 4, 5}, "示例2:k=3,有剩余"),
            new TestCase(new int[]{1, 2, 3, 4, 5}, 1, 
                        new int[]{1, 2, 3, 4, 5}, "k=1,不翻转"),
            new TestCase(new int[]{1, 2, 3, 4, 5}, 5, 
                        new int[]{5, 4, 3, 2, 1}, "k=5,完全翻转"),
            new TestCase(new int[]{1}, 1, 
                        new int[]{1}, "单节点,k=1"),
            new TestCase(new int[]{}, 2, 
                        new int[]{}, "空链表"),
            new TestCase(new int[]{1, 2}, 2, 
                        new int[]{2, 1}, "正好k个节点"),
            new TestCase(new int[]{1, 2, 3}, 2, 
                        new int[]{2, 1, 3}, "一组完整+一个剩余"),
            new TestCase(new int[]{1, 2, 3, 4}, 2, 
                        new int[]{2, 1, 4, 3}, "两组完整"),
            new TestCase(new int[]{1, 2, 3, 4, 5, 6}, 3, 
                        new int[]{3, 2, 1, 6, 5, 4}, "两组完整"),
            new TestCase(new int[]{1, 2, 3, 4, 5, 6, 7}, 3, 
                        new int[]{3, 2, 1, 6, 5, 4, 7}, "两组完整+一个剩余"),
            new TestCase(new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 4, 
                        new int[]{4, 3, 2, 1, 8, 7, 6, 5, 9, 10}, "k=4的复杂情况")
        };
        
        Solution1 solution1 = new Solution1();
        Solution2 solution2 = new Solution2();
        Solution3Simplified solution3 = new Solution3Simplified();
        
        for (int i = 0; i < testCases.length; i++) {
            TestCase testCase = testCases[i];
            System.out.println("测试用例 " + (i + 1) + ": " + testCase.description);
            System.out.println("输入链表: " + Arrays.toString(testCase.input));
            System.out.println("k = " + testCase.k);
            System.out.println("期望结果: " + Arrays.toString(testCase.expected));
            
            // 创建测试链表(每种方法需要独立的链表)
            ListNode head1 = createList(testCase.input);
            ListNode head2 = createList(testCase.input);
            ListNode head3 = createList(testCase.input);
            
            // 测试迭代法
            ListNode result1 = solution1.reverseKGroup(head1, testCase.k);
            int[] array1 = listToArray(result1);
            
            // 测试递归法
            ListNode result2 = solution2.reverseKGroup(head2, testCase.k);
            int[] array2 = listToArray(result2);
            
            // 测试栈法
            ListNode result3 = solution3.reverseKGroup(head3, testCase.k);
            int[] array3 = listToArray(result3);
            
            System.out.println("迭代法结果: " + Arrays.toString(array1));
            System.out.println("递归法结果: " + Arrays.toString(array2));
            System.out.println("栈法结果: " + Arrays.toString(array3));
            
            boolean passed = Arrays.equals(array1, testCase.expected) &&
                           Arrays.equals(array2, testCase.expected) &&
                           Arrays.equals(array3, testCase.expected);
            
            System.out.println("测试结果: " + (passed ? "✅ 通过" : "❌ 失败"));
            System.out.println();
        }
    }
    
    /**
     * 性能测试
     */
    public static void performanceTest() {
        System.out.println("=== 性能测试 ===");
        
        // 创建大链表进行性能测试
        int[] largeArray = new int[10000];
        for (int i = 0; i < largeArray.length; i++) {
            largeArray[i] = i + 1;
        }
        
        Solution1 solution1 = new Solution1();
        Solution2 solution2 = new Solution2();
        Solution3Simplified solution3 = new Solution3Simplified();
        
        int k = 3;
        
        // 测试迭代法性能
        ListNode head1 = createList(largeArray);
        long start1 = System.nanoTime();
        ListNode result1 = solution1.reverseKGroup(head1, k);
        long end1 = System.nanoTime();
        System.out.println("迭代法耗时: " + (end1 - start1) / 1_000_000.0 + " ms");
        
        // 测试递归法性能(可能栈溢出,所以用较小的数组)
        int[] mediumArray = new int[3000];
        for (int i = 0; i < mediumArray.length; i++) {
            mediumArray[i] = i + 1;
        }
        
        ListNode head2 = createList(mediumArray);
        long start2 = System.nanoTime();
        ListNode result2 = solution2.reverseKGroup(head2, k);
        long end2 = System.nanoTime();
        System.out.println("递归法耗时: " + (end2 - start2) / 1_000_000.0 + " ms");
        
        // 测试栈法性能
        ListNode head3 = createList(largeArray);
        long start3 = System.nanoTime();
        ListNode result3 = solution3.reverseKGroup(head3, k);
        long end3 = System.nanoTime();
        System.out.println("栈法耗时: " + (end3 - start3) / 1_000_000.0 + " ms");
    }
    
    /**
     * 测试用例类
     */
    static class TestCase {
        int[] input;
        int k;
        int[] expected;
        String description;
        
        TestCase(int[] input, int k, int[] expected, String description) {
            this.input = input;
            this.k = k;
            this.expected = expected;
            this.description = description;
        }
    }
    
    public static void main(String[] args) {
        runAllTests();
        performanceTest();
    }
}

7. 算法复杂度对比

7.1 详细对比表格

解法时间复杂度空间复杂度实现难度可读性性能表现推荐度
迭代法(预计算)O(n)O(1)中等中等优秀⭐⭐⭐⭐⭐
递归法O(n)O(n/k)简单中等⭐⭐⭐⭐
栈辅助法O(n)O(k)简单中等⭐⭐⭐

7.2 选择建议

推荐使用迭代法的情况:

  • 面试或实际项目中(空间复杂度最优)
  • 对内存使用有严格要求
  • 需要处理大规模数据
  • 追求最佳性能

推荐使用递归法的情况:

  • 学习算法思想,理解分治概念
  • 代码简洁性优先
  • 链表规模中等,不会导致栈溢出
  • 教学演示场景

推荐使用栈法的情况:

  • 初学者理解翻转过程
  • 需要直观的可视化效果
  • k值相对较小的情况

7.3 空间复杂度深入分析

迭代法: O(1)

  • 只使用固定数量的指针变量
  • 不随输入规模增长

递归法: O(n/k)

  • 递归调用栈深度为⌊n/k⌋
  • 当k=1时,空间复杂度为O(n)
  • 当k=n时,空间复杂度为O(1)

栈法: O(k)

  • 栈中最多存储k个节点
  • 空间使用与k成正比,与n无关

8. 常见错误与调试技巧

8.1 常见错误分析

1. 边界条件处理不当

// 错误写法:没有检查k的边界值
public ListNode reverseKGroup(ListNode head, int k) {
    // 缺少k <= 1的检查
    if (head == null) {
        return head;
    }
    // ...
}

// 正确写法:完整的边界检查
public ListNode reverseKGroup(ListNode head, int k) {
    if (head == null || k <= 1) {  // 检查k <= 1的情况
        return head;
    }
    // ...
}

2. 节点连接错误

// 错误写法:连接顺序错误导致链表断裂
ListNode next = current.next;
prev.next = next;          // 错误:丢失了current节点
current.next = next.next;
next.next = current;

// 正确写法:正确的连接顺序
ListNode next = current.next;
current.next = next.next;  // 先断开current与next的连接
next.next = prev.next;     // next指向prev的下一个节点
prev.next = next;          // prev指向next

3. 递归中的连接错误

// 错误写法:连接位置错误
public ListNode reverseKGroup(ListNode head, int k) {
    // ... 检查和翻转前k个节点
    ListNode newHead = reverseFirstK(head, k);
    
    // 错误:应该是head.next而不是newHead.next
    newHead.next = reverseKGroup(current, k);
    return newHead;
}

// 正确写法:正确连接
public ListNode reverseKGroup(ListNode head, int k) {
    // ... 检查和翻转前k个节点
    ListNode newHead = reverseFirstK(head, k);
    
    // 正确:head在翻转后成为尾节点
    head.next = reverseKGroup(current, k);
    return newHead;
}

4. 栈操作中的顺序错误

// 错误写法:没有检查栈的大小
while (current != null) {
    for (int i = 0; i < k && current != null; i++) {
        stack.push(current);
        current = current.next;
    }
    
    // 错误:没有检查栈是否有足够的元素
    while (!stack.isEmpty()) {
        prev.next = stack.pop();
        prev = prev.next;
    }
}

// 正确写法:检查栈大小
while (current != null) {
    for (int i = 0; i < k && current != null; i++) {
        stack.push(current);
        current = current.next;
    }
    
    // 正确:只有当栈中有k个元素时才处理
    if (stack.size() == k) {
        while (!stack.isEmpty()) {
            prev.next = stack.pop();
            prev = prev.next;
        }
    }
}

8.2 调试技巧

1. 添加详细的日志输出

private void debugPrint(String operation, ListNode head, int k, int step) {
    System.out.println("=== " + operation + " (步骤" + step + ") ===");
    System.out.println("当前链表: " + printList(head));
    System.out.println("k = " + k);
    System.out.println();
}

2. 使用断言验证中间状态

// 在关键步骤添加断言
assert prev != null : "prev不应为null";
assert groupTail != null : "groupTail不应为null";
assert k > 0 : "k应该大于0";

3. 分步骤验证链表完整性

private boolean isValidList(ListNode head, int expectedLength) {
    int count = 0;
    ListNode current = head;
    Set<ListNode> visited = new HashSet<>();
    
    while (current != null) {
        if (visited.contains(current)) {
            System.out.println("检测到环!");
            return false;
        }
        visited.add(current);
        count++;
        current = current.next;
    }
    
    if (count != expectedLength) {
        System.out.println("长度不匹配,期望: " + expectedLength + ", 实际: " + count);
        return false;
    }
    
    return true;
}

4. 使用可视化辅助工具

/**
 * 可视化打印链表连接关系
 */
private void printConnections(ListNode head, int limit) {
    ListNode current = head;
    int count = 0;
    
    System.out.print("连接关系: ");
    while (current != null && count < limit) {
        System.out.print(current.val);
        if (current.next != null) {
            System.out.print(" → " + current.next.val);
        } else {
            System.out.print(" → null");
        }
        System.out.print(", ");
        current = current.next;
        count++;
    }
    System.out.println();
}

9. 相关题目与拓展

9.1 LeetCode 相关题目

链表翻转类:

  1. 206. 反转链表:K个一组翻转的基础
  2. 92. 反转链表 II:反转指定区间的链表
  3. 24. 两两交换链表中的节点:K=2的特殊情况
  4. 143. 重排链表:链表的复杂重新排列

分组处理类:

  1. 86. 分隔链表:按值分组重排链表
  2. 328. 奇偶链表:按位置分组
  3. 725. 分隔链表:按长度分组
  4. 817. 链表组件:连通组件问题

递归与分治:

  1. 21. 合并两个有序链表:递归合并
  2. 23. 合并K个升序链表:分治法应用
  3. 148. 排序链表:归并排序的递归实现

9.2 算法模式的扩展应用

1. 分块处理模式

/**
 * 通用的链表分块处理框架
 */
public class LinkedListBlockProcessor {
    public ListNode processInBlocks(ListNode head, int blockSize, 
                                   Function<ListNode, Integer, ListNode> processor) {
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        ListNode current = head;
        
        while (current != null) {
            // 找到当前块的结束位置
            ListNode blockEnd = current;
            for (int i = 1; i < blockSize && blockEnd.next != null; i++) {
                blockEnd = blockEnd.next;
            }
            
            // 处理当前块
            ListNode nextBlock = blockEnd.next;
            blockEnd.next = null; // 临时断开
            
            ListNode processedBlock = processor.apply(current, blockSize);
            
            // 连接处理后的块
            tail.next = processedBlock;
            
            // 移动到处理后块的末尾
            while (tail.next != null) {
                tail = tail.next;
            }
            
            current = nextBlock;
        }
        
        return dummy.next;
    }
}

2. K分组的其他应用

/**
 * K个一组求和
 */
public List<Integer> sumInKGroups(ListNode head, int k) {
    List<Integer> result = new ArrayList<>();
    ListNode current = head;
    
    while (current != null) {
        int sum = 0;
        int count = 0;
        
        // 计算当前k个节点的和
        for (int i = 0; i < k && current != null; i++) {
            sum += current.val;
            count++;
            current = current.next;
        }
        
        if (count == k) {
            result.add(sum);
        }
    }
    
    return result;
}

/**
 * K个一组取最大值
 */
public List<Integer> maxInKGroups(ListNode head, int k) {
    List<Integer> result = new ArrayList<>();
    ListNode current = head;
    
    while (current != null) {
        int max = Integer.MIN_VALUE;
        int count = 0;
        
        for (int i = 0; i < k && current != null; i++) {
            max = Math.max(max, current.val);
            count++;
            current = current.next;
        }
        
        if (count == k) {
            result.add(max);
        }
    }
    
    return result;
}

9.3 实际应用场景

1. 数据流处理

  • 网络数据包的分组处理
  • 日志数据的批量处理
  • 流式计算中的窗口操作

2. 内存管理

  • 内存页面的分组管理
  • 缓存块的批量替换
  • 垃圾回收的分代处理

3. 并行计算

  • 任务的批量分发
  • 数据的分块并行处理
  • MapReduce中的数据分片

4. 数据库操作

  • 批量插入操作
  • 分页查询处理
  • 事务的批量提交

10. 学习建议与总结

10.1 学习步骤建议

第一步:掌握基础知识

  1. 熟练掌握链表的基本操作
  2. 理解指针和引用的概念
  3. 掌握链表翻转的基础算法(LeetCode 206)
  4. 练习两两交换链表节点(LeetCode 24)

第二步:理解分组思想

  1. 理解K个一组的分组概念
  2. 掌握完整组和不完整组的处理差异
  3. 学会使用哨兵节点简化操作
  4. 练习手动模拟翻转过程

第三步:掌握多种解法

  1. 先实现迭代法,理解基本思路
  2. 学习递归法,理解分治思想
  3. 尝试栈法,理解数据结构的辅助作用
  4. 对比不同解法的优缺点

第四步:深入理解和应用

  1. 分析时间和空间复杂度
  2. 学习调试技巧和错误预防
  3. 练习相关的拓展题目
  4. 理解算法在实际中的应用

10.2 面试要点

常见面试问题:

  1. “请实现K个一组翻转链表”
  2. “能否优化空间复杂度到O(1)?”
  3. “递归和迭代方法有什么区别?”
  4. “如何处理链表长度不是k的倍数的情况?”
  5. “这个算法可以用在什么实际场景中?”

回答要点:

  1. 多种解法:能够提供迭代、递归、栈三种解法
  2. 复杂度分析:准确分析时间和空间复杂度
  3. 边界处理:展示对各种边界情况的考虑
  4. 代码质量:代码简洁、逻辑清晰、注释完整
  5. 实际应用:能够联系实际应用场景

10.3 常见陷阱与注意事项

1. 指针操作顺序

// 必须按正确顺序修改指针,避免链表断裂
// 正确顺序:先断开要移动的节点,再建立新连接

2. 边界条件遗漏

// 必须考虑:空链表、k=1、链表长度小于k、k等于链表长度等情况

3. 递归深度问题

// 当k=1或链表很长时,递归可能导致栈溢出
// 需要根据实际情况选择合适的解法

4. 内存泄漏风险

// 在某些语言中,要注意避免循环引用
// Java有垃圾回收,但仍要注意指针的正确性

10.4 实际应用价值

  1. 算法思维训练:培养分组处理和分治思想
  2. 指针操作精通:提高复杂指针操作的准确性
  3. 递归理解深化:理解递归在链表问题中的应用
  4. 工程实践能力:学会选择合适的算法和数据结构

10.5 扩展学习方向

1. 高级链表算法

  • 跳表的实现和应用
  • LRU缓存的链表实现
  • 并发链表的设计

2. 分治算法深入

  • 归并排序在链表上的实现
  • 快速排序的链表版本
  • 分治法在树结构中的应用

3. 系统设计中的应用

  • 分布式系统中的数据分片
  • 负载均衡算法的设计
  • 缓存系统的分层设计

10.6 最终建议

  1. 多练习:通过大量练习巩固各种解法
  2. 画图理解:通过画图理解复杂的指针操作过程
  3. 对比学习:对比不同解法的优缺点和适用场景
  4. 举一反三:将学到的技巧应用到其他链表问题
  5. 注重实践:在实际项目中寻找应用场景

总结:
K个一组翻转链表是链表操作中的经典难题,它完美地结合了分组思想、指针操作和算法设计技巧。通过这道题,我们不仅可以掌握链表的高级操作技能,还能学习到重要的算法设计模式,如分治思想、递归设计、迭代优化等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值