Skip to content

K 个一组翻转链表

约 720 字大约 2 分钟

2025-02-28

25. K 个一组翻转链表

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

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

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

示例 1:

img.png

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

递归+双指针

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null) return null;
        
        // 区间 [a, b) 包含 k 个待反转元素
        ListNode a, b;
        a = b = head;
        
        // k次操作,即从头节点向后走k步,到达第k+1个节点。重点,左闭右开。
        for (int i = 0; i < k; i++) {
            // 不足 k 个,不需要反转,base case。注意先判空再前进
            // 假设链表长度为k,b会前进k步,到达null,此时也应该反转,
            // 但是如果先前进再判空的话就会直接返回head,不会反转。
            if (b == null) return head;
            b = b.next;
        }
        
        // 反转前 k 个元素
        ListNode newHead = reverse(a, b);
        // 递归反转后续链表并连接起来
        a.next = reverseKGroup(b, k);
        return newHead;
    }

    /* 反转区间 [a, b) 的元素,注意是左闭右开 */
    // 不能用左闭右闭,即[a,b-1],区间里只有k个节点。
    // 因为如果是[a,b-1],那么while的结束条件应该是cur!=null,但是这样就会一直走到链表末尾才停止循环
    // 而不是走到b停止循环,因此这里的b和原来的null一样,相当于一个结束循环的标志点。
    ListNode reverse(ListNode a, ListNode b) {
        ListNode pre, cur, nxt;
        pre = null;
        cur = a;
        nxt = a;
        
        // while 终止的条件改一下就行了。正常是[a, null),cur != null。重点。
        while (cur != b) {
            // 备忘
            nxt = cur.next;
            // 反转。cur==b-1时,nxt=b,cur.next=pre即把b-1指向b-2,所以b-1和b之间就没有连接了,所以[a,b-1]就和后面分开了。
            cur.next = pre;
            // 前进
            pre = cur;
            cur = nxt;
        }
        // 返回反转后的头结点
        return pre;
    }
}
  • 时间复杂度:O(n),其中 n 为链表的长度。head 指针会在 O(nk)O\left(\left\lfloor\frac{n}{k}\right\rfloor\right) 个节点上停留,每次停留需要进行一次 O(k) 的翻转操作。
  • 空间复杂度:O(1),我们只需要建立常数个变量。