Skip to content
本页目录

反转链表篇

整表反转

剑指 Offer II 024. 反转链表

迭代法

解题思路

  • 若表为null或长为1,直接返回表头head
  • 初始[prev, curr] = [null, head],当curr不为空时,局部依次交换节点直到反转完成。
JavaScript
const reverseList = head => {
  if (!head || !head.next) return head
  let [prev, curr] = [null, head]
  while (curr) {
    const next = curr.next  // 获取下一节点
    curr.next = prev  // 局部反转
    prev = curr  // 缓存当前节点
    curr = next  // 更新当前节点
  }
  return prev
}

时间复杂度:O(n);空间复杂度:O(1)

递归法

解题思路

先局部反转head.next.next = head,再切断进栈前的连接head.next = null,避免成环。

JavaScript
const reverseList = head => {
  if (!head || !head.next) return head
  const newHead = reverseList(head.next)
  head.next.next = head   // 出栈时,将当前节点连接至上一节点(局部反转)
  head.next = null  // 切断当前节点进栈时的连接,防止循环引用
  return newHead
}
/* 
假设初始链表为:1->2->3->4
递归时,在递的过程中,当递到尾节点 (head === 4) 时,
开始递归中的归,此时依次返回的head为4,3,2,1
head.next.next = head 即 4.next.next = 4 (局部反转 5.next = 4)
*/

时间复杂度:O(n);空间复杂度:O(n)

区间反转

92. 反转链表 II

穿针引线法

解题思路

首先设置虚拟头节点dummy

  • prev = dummy开始,先迭代l-1步找到l对应的节点left及前一节点prev
  • 再迭代r-l+1步找到r对应的节点rightright.next,然后从原链切出区间[left, right]
  • 将区间[left, right]反转后,重新拼接成链。

返回 dummy.next

JavaScript
const reverseBetween = (head, l, r) => {
  const dummy = new ListNode(-1, head)
  let prev = dummy
  // 遍历 l-1 步找到 l 的前驱节点
  let cnt = l
  while (cnt-- > 1) {
    prev = prev.next
  }
  // 待反转区间[left, right]
  let [left, right, k] = [prev.next, prev, r - l + 1]
  while (k-- > 0) {
    right = right.next
  }
  let curr = right.next
  // 切出区间
  prev.next = null
  right.next = null
  // 做反转,并拼接
  prev.next = reverseList(left)
  left.next = curr
  return dummy.next
}

时间复杂度:O(n);空间复杂度:O(1)

头插法

解题思路

首先设置虚拟头节点dummy,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置,我们使用三个指针变量prevcurrnext 来记录反转的过程中需要的变量,它们的意义如下:

  • curr:指向待反转区域的第一个节点l
  • next永远指向curr的下一个节点,循环过程中,curr变化以后next会变化;
  • prev:永远指向待反转区域的第一个节点l的前一个节点,在循环过程中不变。

具体操作步骤:

  1. 先将curr的下一个节点记录为next
  2. curr的下一个节点指向next的下一个节点;
  3. next的下一个节点指向prev的下一个节点;
  4. prev的下一个节点指向next

返回 dummy.next

JavaScript
const reverseBetween = (head, l, r) => {
  const dummy = new ListNode(-1, head)
  let prev = dummy
  // 遍历 l-1 步找到 l 的前驱节点
  let cnt = l
  while (cnt-- > 1) {
    prev = prev.next
  }
  // 反转区间[l, r]
  let [curr, k] = [prev.next, r - l]
  while (k-- > 0) {  // 头插法
    const next = curr.next  // 步骤1
    curr.next = next.next  // 步骤2
    next.next = prev.next  // 步骤3
    prev.next = next  // 步骤4
  }
  return dummy.next
}

时间复杂度:O(n);空间复杂度:O(1)

K个一组反转

25. K 个一组翻转链表

穿针引线法(K个一组)

解题思路

设置虚拟头节点dummy,迭代K次,找到该组最后一个节点last,判断当前last是否为空:

  • 为空则跳出break
  • 否则缓存K组的开始节点及其下一节点并切断链表let [curr, next] = [prev.next, last.next]; last.next = null, 然后反转K组节点并拼接,最后更新节点prev = curr;last = prev;

返回dummy.next

JavaScript
const reverseKGroup = (head, k) => {
  const dummy = new ListNode(-1, head)
  let [prev, last] = [dummy, dummy]
  while (last.next) {  // k个一组
    for (let i = 0; i < k && last != null; i++) {
      last = last.next
    }
    if (last == null) break
    // 区间反转
    let [curr, next] = [prev.next, last.next]
    last.next = null  // 切断
    prev.next = reverseList(curr)
    curr.next = next
    // 更新至下一区间
    prev = curr
    last = prev
  }
  return dummy.next
}

时间复杂度:O(k*n);空间复杂度:O(1)

偶数长度反转

2074. 反转偶数长度组的节点

迭代法

解题思路

设置虚拟头节点dummy,在迭代中从0开始计算链表节点数cnt及当前组的节点数len,并判断len的奇偶性len & 1

  • len为奇数,则以len区间,直接更新节点curr = curr.next; prev = prev.next跳过当前组;
  • len为偶数,则以len区间,头插法反转该组子链表并更新节点prev = curr;curr = curr.next

返回dummy.next

JavaScript
const reverseEvenLengthGroups = head => {
  const dummy = new ListNode(-1, head)
  let [prev, curr, cnt] = [dummy, dummy.next, 0]
  while (curr) {
    cnt++
    let [len, subCurr] = [0, curr]
    // 计算 len
    while (len < cnt && subCurr) {
      len++
      subCurr = subCurr.next
    }
    if (len & 1) {  // len 为奇数就跳过
      while (len-- > 0) {
        curr = curr.next
        prev = prev.next
      }
    } else {  // len 为偶数
      while (len-- > 1) {  // 头插法
        const next = curr.next
        curr.next = next.next
        next.next = prev.next
        prev.next = next
      }
      prev = curr
      curr = curr.next
    }
  }
  return dummy.next
}

时间复杂度:O(n);空间复杂度:O(1)