剑指 Offer II 026. 重排链表(快慢指针、链表翻转和合并链表)

99 阅读2分钟

每日刷题第17天 2021.1.12

重排链表

  • 难度:中等
  • 方法:链表

题目

  • 给定一个单链表 L 的头节点 head ,单链表 L 表示为:L0 → L1 → … → Ln-1 → Ln 
  • 请将其重新排列后变为:L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
  • 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例

  • 示例1 image.png
输入: head = [1,2,3,4]
输出: [1,4,2,3]
  • 示例2 image.png
输入: head = [1,2,3,4,5]
输出: [1,5,2,4,3]

提示

  • 链表的长度范围为 [1, 5 * 104]
  • 1 <= node.val <= 1000

解法

  • 快慢指针 slow fast,寻找中点
    • 可以采取建立两个指针,一个指针一次遍历两个节点,另一个节点一次遍历一个节点,当快指针遍历到空节点时,慢指针指向的位置为链表的中间位置,这里解决问题的算法称为「快慢指针」
  • 找到中点之后,将后半部分的链表进行翻转
  • 前后链表,进行连接即可
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {void} Do not return anything, modify head in-place instead.
*/
var reorderList = function(head) {
// 解法: 先快慢指针,再反转后半段链表,之后将两个链表进行合并
// 快慢指针找到中点
let slow = head;
let fast = head;
// 记录中点的前一个节点
let pre = null;
// 记录链表的长度
let len = 1;
while (null != fast.next) {
  if (fast.next.next != null) {
    // 快指针跳2
    fast = fast.next.next;
    len += 2;
  }else {
    fast = fast.next;
    len++;
  }
  // 慢指针跳1
  pre = slow;
  slow = slow.next;
}
// 循环结束后,fast指针为尾节点,slow指针为中间节点
if (len == 1) {
  return head;
}
// 获取前半段链表
pre.next = null;
// slow指针记录了后半段的链表,现在将其进行反转reverse,使用三个指针
function reverse(slow) {
  let prev = null;
  let cur = slow;
  let next = null;
  while (cur) {
    next = cur.next;
    cur.next = prev;
    prev = cur;
    cur = next;
  }
  return prev;
}
slow = reverse(slow);
// 将两个链表进行合并
let ans = new ListNode();
ans.val = 111;
let ansH = ans;
while (head || slow) {
  if (head) {
    ans.next = head;
    head = head.next;
    ans = ans.next;
  }
  if (slow) {
    ans.next = slow;
    slow = slow.next;
    ans = ans.next;
  }
}
return ansH.next;
};

附录

  • 快慢指针:寻找链表的中点。
    • 原理:fast和slow指针,让fast移动的位置是slow移动的两倍,这样当fast指到链表末尾时,slow刚好指到链表的中点。
  • 翻转链表:三指针prev、cur、next
  • 合并链表:⚠️一定要记住生成的链表头