25. K 个一组翻转链表

348 阅读4分钟

题目描述

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

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

分析

输入:链表头节点 head, 分组长度 k 输出:翻转之后的链表头节点

解题思路

这题实际上考察的是链表操作的时候,你能不能考虑到所有的细节,这个题目里我们可以抽出下面几个知识点:链表翻转,保持链表节点的引用,dummyHead 的使用

我们在解题的过程再去看具体的知识点。

那我们先说下思路,既然是 k 个为一组,那我们肯定可以把每 k 个 node 作为一组翻转的节点放在一起,然后以每组的 head 来作为翻转开始的节点。

那么在开始翻转之前,我们要看接下来待翻转的这组节点的数量到底还够不够 k 个,如果不够的话,就不翻转了,这块在代码里肯定是个 break while 循环♻️的过程。

这就是整体的思路,在分析完整体的思路之后,我们可以看看,到底怎么翻转呢?

image.png

那比如我们要像图片里那样翻两个节点,很显然,我们在需要一个翻转链表方法之余,也需要注意⚠️前边一个节点和后边一个节点。然而还需要注意的一个问题呢,是我们需要考虑💭只有一个节点的情况,这也是链表问题都需要关注的。

解决链表长度为 1 的问题,我们可以用一个 dummyHead 解决,这样删除、翻转 head 的时候,我们都不需要判断长度 1 的情况了~

那我们着重看下翻转,翻转的代码可以写成一个模版,每次遇到翻转链表的问题时,就用固定的套路就可以了。

设置一个前驱节点 pre,当前节点 cur,先让 pre = null

举个例子,我要翻转下面这个链表, k = 2:

image.png 那如果我要翻 3,4 两个节点的话,实际上就是要让 4 的 next 指向 3, 3 的 next 再指向 3 的前一个节点,然后把要操作的区间整体向后移动一个,也就是 pre = 4, cur = cur.next

所以很明显 3 的前一个节点是 null,也就是 pre 的初始值。

我们用代码表示下翻转的过程

function reverse(head) {
        // 把前驱节点设成 null,当前节点 cur 设为 head
        let cur = head,
          pre = null;

        // 开始翻转的过程
        // 可以理解为 while 循环一次,那么 cur 指向的节点的 next,我们会把它给改成指向
        // pre
        // 所以每个 while 循环结束后,都会让 cur 向前走一步,继续改变后边 node 的 next
        // 直到 cur 指向了 null
        while (cur) {
          const tmp = cur.next

          cur.next = pre
          pre = cur
          cur = tmp
        }

        return pre
      }

那我们考虑下前后两个节点的问题。

在指定的区间翻转完毕之后,我们可以看到 cur 是整个区间后边的第一个节点,我们应该让还未翻转时候的 head.next 指向这个 node。

再翻转结束后,我们还需要返回新的 head,并且让被翻转链表的前驱指针的 next 指向它。

最后,我们考虑下翻转之前去判断剩余节点够不够 k

我们来看完整代码:

代码

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function (head, k) {
  if (!head) return null;
  const dummyHead = new ListNode(null, head);
  let pre = dummyHead;

  do {
      // 不停地翻转链表
    pre.next = reverseList(pre.next, k);

// 直到剩余节点数量不够 k 个,这个需要在每次翻转后检查一下,翻转之前检查
    for (let i = 0; i < k; i++) {
    // 如果有 k 个,就一直向后移动 pre,直到找到下一个翻转的 head
      pre = pre && pre.next;
    }

    if (!pre) break;
  } while (true);

// 翻转节点的代码
  function reverseList(head, k) {
    let pre = head,
      cur = head,
      count = k;

    // Check if remaining nodes serves k, including cur
    while (--count) {
      pre = pre && pre.next;
    }
    if (!pre) return head;

    pre = null;
    while (k--) {
      const tmp = cur.next;

      cur.next = pre;
      pre = cur;
      cur = tmp;
    }
    
    // 指向后继节点
    head.next = cur;

    return pre;
  }

// 返回 dummyHead 的 next,这是为了处理原始的 head 节点被处理的情况,很方便~
  return dummyHead.next;
};