Skip to content

6.K个一组反转链表

LeetCode 25. K 个一组翻转链表

题目描述

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

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

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

示例 1:

alt text

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

输出:[2,1,4,3,5]

示例 2:alt text

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

提示:

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

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

解题思路

思路一:

我们每次翻转 K 个节点前,可以使用一个函数用于判断剩余节点是否足够翻转,如果不够翻转,则直接返回原链表;如果足够,返回翻转链表后的头节点和尾节点。

js
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function (head, k) {
  // 处理边界条件
  if (!head.next) return head;
  // 翻转以p 为头的前 n 个节点
  function reverseN(p, n) {
    let cur = p;
    let next;
    let pre = null;
    for (let i = 1; i <= n; i++) {
      next = cur.next;
      cur.next = pre;
      pre = cur;
      cur = next;
    }
    // 假设原来 3-4-5,3,4 翻转后为 4-3,3需要指向 5,下面这行代码就是这个作用
    p.next = cur;
    // 返回翻转后的头节点和尾节点
    return [pre, p];
  }

// 获取下一个需要翻转的头节点
  function getNextHead(head, n) {
    let count = 0;
    while (head) {
      count++;
      if (count === n) {
        break;
      }
      head = head.next;
    }
    return head;
  }

  let p = head;
  let preEnd;
  let firstHead;
  while (p) {
    const nextp = getNextHead(p, k);
    const next = nextp?.next;
    // nextp 有值,说明 k 个节点可以翻转
    if (nextp) {
      // 获取翻转后的头尾节点
      const [start, end] = reverseN(p, k);
      // 记录头节点
      if (!firstHead) {
        firstHead = start;
      }
      // 记录尾节点,连接到新翻转的头节点
      if (preEnd) {
        preEnd.next = start;
      }
      // 尾节点记录
      preEnd = end;
      // 指针移动到下一个需要翻转的头节点
      p = next;
    } else {
    // nextp 为 null 表示剩余节点不够翻转 直接结束即可
      break;
    }
  }
  return firstHead;
};

这个解法主要是两个核心点,第一个是每k个节点翻转后,头尾需要正确的连接,第二个是记录翻转后的头尾节点,方便连接到下一次翻转。

思路二:

这个方法使用递归,我们递归翻转 head 开头的 n 个节点,返回新的头节点,新的头节点等于递归返回的节点。

  • 设计一个函数,翻转 a 到 b 区间的元素,返回新的头节点
  • 递归翻转 head 后面的 k 个节点,返回新的头节点
  • 连接 a 到递归返回的节点,返回新的头节点
js
function reverse(a, b) {
  let pre = null,
    cur = a,
    next;

  while (cur !== b) {
    // 存储下一个
    next = cur.next;
    // 修改当前的 next 指向 pre
    cur.next = pre;
    // pre 指向当前
    pre = cur;
    // cur 指向下一个
    cur = next;
  }
  return pre;
}

var reverseKGroup = function (head, k) {
  // 空值处理
  if (head === null) return head;
  // reverse a,b 区间的元素
  // 区间节点 b是下一组的头节点
  let a, b;
  // 开始默认头节点
  a = b = head;
  // b 往后走,看看是否够 k 个
  for (let i = 0; i < k; i++) {
    if (b === null) return a;
    b = b.next;
  }
  let newhead = reverse(a, b);

  // a 的 next 指向下一个头节点
  a.next = reverseKGroup(b, k);
  return newhead;
};

总结

两种方法都可以解决这个问题,思路一使用了函数,思路二使用了递归。 核心思路都是 K 个 去处理,然后处理指针的指向问题就能解决。