xxleyi / loop_invariants

记录以及整理「循环不变式」视角下的算法题解

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

leetcode 25. K 个一组翻转链表

xxleyi opened this issue · comments

题:

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

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

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

 
示例:

给你这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

 
说明:

你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-nodes-in-k-group
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解:

此题难度被 leetcode 认定为 hard,但粗略读过问题之后,发现解题思路并不复杂,只是细节会比较多。

细节多主要体现在维持「循环不变式」需要的变量较多。最终实现之后,我使用了 6 个变量,且没有发现可以精简的余地。

除此之外,还需要在头部和尾部,根据特定的条件进行特殊处理,这也算是细节多的一部分。

如果还要再加一点,就是最后一段无法整除,也就是不足 k 个节点时该如何处理的细节。思路是再次翻转过来,但如果不考虑的多一点,很可能会另写一个单独的逻辑。但稍加考虑之后会发现,可以很好的复用已经写好的逻辑。

这道题很贴近实际工作的任务:思路不难,比较直接,但涉及变量较多,细节上的处理容易出漏洞,逻辑容易陷入混乱和冗余

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
  let i = 0
  let cur = head
  let prev = null
  let prevSegmentTail = null
  let curSegmentTail = head
  let newHead = null

  while (cur) {
    if (i < k) {
      i++
      const oldPrev = prev
      const curNext = cur.next
      prev = cur
      prev.next = oldPrev
      cur = curNext
    } 
    else {
      // 单独处理 newHead
      !newHead && (newHead = prev)
      // prevSegmentTail 为空时也需要单独处理
      prevSegmentTail && (prevSegmentTail.next = prev)
      prevSegmentTail = curSegmentTail
      curSegmentTail = cur
      prev = null
      i = 0
    }
  }

  if (i === k) {
    // 恰好整除时的处理
    // 多过一段的处理
    if (prevSegmentTail) prevSegmentTail.next = prev
    // 只有一段的处理
    else newHead = prev
  } else {
    // 非整除部分的处理:复用已有逻辑,再次翻转过来,达到负负为正的目的
    prevSegmentTail.next = reverseKGroup(prev, i)
  }

  return newHead
};