用JavaScript刷leetcode第92题-反转链表II(部分反转)

426 阅读1分钟

前言

以三种迭代的方式来解决这道题

  1. 拆合:拆成 (左 + 普通反转 + 右),反转后,连接起来
  2. 拆合:拆成(左 + 反转前n个),反转后,连接起来
  3. 头插法:我理解就是 (存、删、增)

一、题目描述

题目地址
git代码

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

二、解题

2.1 方法一、拆合(左 + 普通反转 + 右)

2.1.1 思路

  • 分成三部分 - 左、普通反转、右
  • 记录四个节点 待反转头节点、待反转头节点的前一个节点、待反转反转尾节点、待反转尾节点的后一个节点
  • 反转需要反转的部分,反转后,头尾位置互换,这里划重点
  • 拼接:待反转头节点的前一个节点 + 待反转反转尾节点(即反转后反转部分的头节点),待反转头节点的前一个节点(即反转后反转部分的尾节点) + 待反转尾节点的后一个节点

2.1.2 代码

const reverseBetween = function (head, left, right) {
  // 虚拟头节点 减少边界判断
  let hair = new ListNode(-1, head)

  // 反转头节点的前一个节点
  let pre = hair
  // 找到pre
  for(let i = 0; i < left - 1; i++) {
    pre = pre.next
  }
  
  // 反转尾节点
  let end = pre
  // 找到end
  for(let i = 0; i < right - left + 1; i++) {
    end = end.next
  }

  // 反转头节点
  const start = pre.next
  // 反转尾节点的后一个节点
  const suc = end.next

  // 断开连接
  pre.next = null
  end.next = null

  // 反转
  reverse(start)

  // 反转后的拼接
  pre.next = end
  start.next = suc

  // 返回
  return hair.next
}
// 普通反转
function reverse(head) {
  // 空链表和只有一个节点的链表不需要反转
  if(head === null || head.next === null)  return false

  let pre = null
  let cur = head
  while(cur !== null) {
    const next = cur.next
    cur.next = pre
    pre = cur
    cur = next
  }
}

2.2 方法二、拆合(左 + 反转前n个)

2.2.1 思路

这个方法与方法一差不多,不想过多介绍。只不过这个方法拆成了两个部分,然后拼接,具体就直接看我代码注释吧

2.2.2 代码

// 解法二:拆合-拆成两部分(left前面 + 反转前n个节点)与解法一实际差不多
const reverseBetween = function (head, left, right) {
  // 虚拟头节点,减少边界判断
  let hair = new ListNode(-1, head)
  // 反转头节点 的 前一个节点
  let pre = hair

  // 计数,left 后 反转前n 个节点
  const n = right - left + 1

  // 找到pre
  while(left - 1 > 0) {
    pre = pre.next
    left--
  }

  // 反转前n个节点,返回反转头
  let p = reverse(pre.next, n)


  // 拼接 pre + 反转头
  pre.next = p

  // 返回结果
  return hair.next
  
}


// 返回头节点
function reverse(head, n)  {
  let pre = null
  // 记录未反转的头
  let cur = head
 
  // 反转前n个节点
  while(n > 0) {
    const next = cur.next
    cur.next = pre
    pre = cur 
    cur = next
    n--
  }

  // 此时head 是 反转部分的尾,cur 是 反转尾的下一个节点
  head.next = cur

  return pre
}

2.3 方法三、头插法

2.3.1 思路

  • 找到待反转部分头节点的前一个节点,记录
  • 依次遍历反转部分节点,将每个节点插到头部
  • 具体步骤:存、删、增
  • 存:目的是保证删后不丢失
  • 删:把当前节点的下一个节点删掉
  • 增:把保存的节点即刚才删掉的节点直接插入到反转的头部

2.3.2 代码

const reverseBetween = function (head, left, right) {
  // 虚拟头节点 ,用来减少边界判断
  let hair = new ListNode(-1, head)
  // 反转部分头节点前一个节点
  let pre = hair
  
  for(let i = 0; i < left - 1; i++) {
    pre = pre.next
  }

  // 待反转头部
  let cur = pre.next

  // 头插法,将 next 插到 反转部分的头
  for(let i = 0; i < right - left + 1; i++) {
    // 保存next
    const next = cur.next
    // 删除next
    cur.next = next.next
    // 往反转头增加next
    next.next = pre.next
    // 拼接
    pre.next = next
  }

  return hair.next
}