Q15- LeetCode92-反转链表 II

75 阅读3分钟

实现思路

方法1.1 迭代- 反转链表节点- 标准反转方法

1 反转链表递归标准实现- 图示法

方法1.2 迭代- 反转链表节点- 头插法

1 设置锚点 和 当前待移动节点的 前一个节点

2 不断把 当前待移动节点的移动到锚点之后,从而形成反转

方法2.2- 递归- 转化为reverseTopN

1 关键是要逐步缩小问题规模,从reverseBetween 转化为 reverseTopN

2 left 其实是指相对链表头偏移了多少个节点; right指的是 相对left有多少个节点需要被反转

参考文档

1.1- 方法1.1参考实现

1.2- 方法1.2图示理解

2.1- 方法2.2逐步理解

代码实现

方法1.1- 迭代- 标准反转链表节点方法 时间复杂度: O(n) 空间复杂度: O(1)

function reverseBetween(head, left, right) {
  let dummy = new ListNode(-1, head)
  // p0固定指向 反转区间的前一个节点
  let p0 = dummy
  for (let i = 0; i < left - 1; i++) {
    p0 = p0.next
  }
  // 对反转区间进行 标准节点反转
  let pre = null, cur = p0.next
  for (let i = 0; i < right - left + 1; i++) {
    let willNext = cur.next
    cur.next = pre
    pre = cur
    cur = willNext
  }
  // 执行到此时,pre是反转区间的新头节点;cur是反转区间之后的那个节点,即
  // 原链表:           1  --> 2 --> 3  --> 4  -->  5
  // 反转了2~4之间的链表:1  --> 2 <-- 3  <-- 4  -->  5
  //                          |
  //                         null
  //                   p0                  pre    cur

  // 期望的最终结果链表:  1 --> 4  --> 3 -->  2  -->  5
  // 因此,接下来这2步,就是 很自然的连接 新的首尾节点即可
  p0.next.next = cur
  p0.next = pre
  return dummy.next
}

方法1.2- 迭代- 头插法 时间复杂度: O(n) 空间复杂度: O(1)

function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
  if (!head || !head.next) return head;
  let dummy = new ListNode(-1, head)
  let p0 = dummy, cur = dummy.next
  for (let i = 0; i < left - 1; i++) {
    p0 = p0.next
    cur = cur.next
  }
  for (let i = 0; i < right - left; i++) {
    let willMove = cur.next
    cur.next = willMove.next
    // 易错点:此时应该让待移动的头插节点,移动到最前面,也就是p0之后
    willMove.next = p0.next
    p0.next = willMove
  }
  return dummy.next
};

方法2.1- 递归- depth判断法 时间复杂度: O(n) 空间复杂度: O(n)

function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
  if (!head || !head.next) return head;
  if (left === right) return head;
  return reverse(head, left, right, 1)[0];
};

function reverse(head, left, right, depth) {
  if (depth === right) {
    const tail = head.next;
    // 易错点1: 新的头节点 head.next不能是null
    // 而是要指向自己,这样才能保证后续反转节点 不会是空指针
    head.next = head;
    return [head, tail];
  }
  const [newHead, newTail] = reverse(head.next, left, right, depth + 1);
  if (depth <= left - 1) {
    head.next = newHead;
    // 易错点2: 要保证递归的返回格式是统一的
    return [ head ];
  } else if (depth >= left) {
    // 反转区间的情况:进行节点反转
    head.next.next = head;
    head.next = depth === left ? newTail : null;
    return [newHead, newTail];
  }
}

方法2.2- 递归- 转化为reverseTopN 时间复杂度: O(n); 空间复杂度(n)

let successor = null
function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
  //S3 递归终止条件:相当于是 反转了链表的前n个节点,返回最新的头节点
  if (left === 1) {
    return reverseTopN(head, right)
  }
  //S1 递归含义:反转一个链表的部分节点,返回反转后的新头节点
  let newHead = reverseBetween(head.next, left-1, right-1)
  //S2.1 本轮操作:反转了部分节点后,让当前节点连接已经反转好的新链表头即可
  head.next = newHead
  //S2.2 返回拼接了本轮head的全规模链表,就是最终结果
  return head
};

// 反转了链表的前n个节点,返回最新的头节点
function reverseTopN(head: ListNode, n: number) {
  // S3 处理递归中止条件
  if (n === 1) {
    successor = head.next
    return head
  }
  //S1 缩小为 子规模
  let newHead = reverseTopN(head.next, n-1)
  //S2 本轮数据处理
  head.next.next = head
  head.next = successor
  return newHead
}