链表 - 反转链表

216 阅读4分钟

一、反转链表(简单)

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

来源: Leecode - 反转链表

分析

链表是一种线性表,它不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针。每个元素实际上是一个单独的对象,所有的对象都是通过每个元素中的引用字段链接在一起。考虑需要反转链表,那么只需要将当前指针的 next 指向上一个节点,上一个节点的 next 指向当前节点,便可以反转链表。对于头结点来说,没有上一个节点,需要我们定义一个空节点来表示上一个节点,分析图如下:

reverse1.jpg

代码实现

/**
 * 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 {ListNode}
 */
var reverseList = function (head) {
  // 定义上一个节点,用于承载链表头结点反转使用
  let pre = null;
  // 定义当前节点指向头结点
  let cur = head;

  // 遍历链表
  while (cur) {
    // 储存下一个节点
    const next = cur.next;
    // 当前节点的next指向上一个节点
    cur.next = pre;
    // 上一个节点指向当前节点
    pre = cur;
    // 继续下一个节点遍历
    cur = next;
  }
  // 返回反转后的链表
  return pre;
};

二、反转链表 - 两两交换链表中的节点(中等难度)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

swap_ex3.jpeg

示例 1:

输入:head = [1,2,3,4] 输出:[2,1,4,3] 示例 2:

输入:head = [] 输出:[] 示例 3:

输入:head = [1] 输出:[1]

来源: Leecode - 两两交换链表中的节点

分析

从上图中知道,需要两两交换节点,并要求在原链表上操作,分解步骤如下:

  1. 创建一个空节点 dummy,dummy.next 指向 head,用于 head 节点改变后能找到头结点;
  2. 遍历链表,一组节点的交换需要三个步骤:

    当前节点的 next 节点指向下一个节点的 next; 下一个节点的 next 指向当前节点; 上一个节点 pre 的 next 指向下一个节点;

  3. 更新 pre 节点指针为 head,即目前的第二个节点;
  4. 指针推进,准备交换下一组节点; ........
  5. 返回 dummy.next,即交换后的头结点;

如图:

swap_ex2.jpg

代码实现

/**
 * 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 {ListNode}
 */
var swapPairs = function (head) {
  // 创建个dummy节点
  let dummy = new ListNode();
  // dummy的next指向head节点
  dummy.next = head;
  // 初始化上一个为dummy
  let pre = dummy;

  // 遍历head节点
  while (head && head.next) {
    // 缓存head的下一个节点
    const next = head.next;
    // 第一步,将当前节点指向下下个节点
    head.next = next.next;
    // 第二步,下个节点的next指向当前节点
    next.next = head;
    // 第三步,上一个节点next指向下一个节点
    pre.next = next;

    // 同步上个节点指向每组的第二个节点
    pre = head;
    // 下一次循环
    head = head.next;
  }

  // 返回dummy的next节点,即新节点的头结点
  return dummy.next;
};

反转链表 3 - 局部反转 (中等难度)

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

swap_ex1.jpeg

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4 输出:[1,4,3,2,5] 示例 2:

输入:head = [5], left = 1, right = 1 输出:[5]

来源: Leecode - 局部反转链表

分析

从 left 位置开始到 right 位置局部反转,需要定位反转区域的上一个节点leftHead,第一个节点为pre, 第二个节点为cur, 第三个节点为next,根据反转链表的思路执行反转进行反转即可,具体步骤:

  1. 初始化pre、cur、leftHead,定义dummy空节点,dummy.next 指向head,初始化p节点指向 dummy;
  2. 从第一个节点开始遍历到 left 的上一个节点,定义为leftHead,设置start指向 leftHead,即开始反转的第一个节点,pre 指向 start,cur 指向 pre.next;
  3. 遍历从 left 开始到 right 结束,反转这个区域的节点;
  4. 将 leftHead.next 指向 pre,即反转后的最后一个节点;
  5. 将 start.next 指向 cur,cur 为 right 的下一个节点;
  6. 返回 dummy.next;

如图:

reverse3.jpg

代码实现

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} left
 * @param {number} right
 * @return {ListNode}
 */
var reverseBetween = function (head, left, right) {
  // 如果没有节点,或者只有一个节点,原样返回
  if (!head || !head.next) {
    return head;
  }
  // 初始化当前节点、上一个节点、反转链表左侧第一个节点
  let cur, pre, leftHead;
  // 定义dummy节点
  let dummy = new ListNode();
  // dummy的next指向头结点
  dummy.next = head;

  // 缓存dummy节点,复制给p
  let p = dummy;
  // 链表p走到左侧开始反转节点
  for (let i = 0; i < left - 1; i++) {
    p = p.next;
  }
  // 定义反转链表前结点
  leftHead = p;
  // 定义第一个节点
  let start = leftHead.next;
  // 定义上一个节点
  pre = start;
  // 初始化当前节点
  cur = pre.next;

  // 从left到right反转设置
  for (let i = left; i < right; i++) {
    let next = cur.next;
    cur.next = pre;
    pre = cur;
    cur = next;
  }

  // 将反转链表的上一个指向反转后的第一个
  leftHead.next = pre;
  // 反转后的最后一个节点指向后面未反转的链表
  start.next = cur;

  return dummy.next;
};