发布于 2026-01-06 1 阅读
0

链表反转的图示指南

链表反转的图示指南

本文最初发表于https://algodaily.com,我在该网站上维护一个技术面试课程,并为有抱负的开发者撰写思考性文章。

你收到一串linked list数字,但顺序与你需要的相反。这种情况已经发生多次,所以你决定编写一个算法来反转收到的列表。你收到的列表如下:



// 17 -> 2 -> 21 -> 6 -> 42 -> 10


Enter fullscreen mode Exit fullscreen mode

编写一个算法,reverseList该方法接受一个head节点作为参数,并反转链表。该算法应能够反转任意长度的链表。

您可以将此示例linked list用于测试目的。您的方法将按如下方式调用:



class LinkedListNode {
  constructor(val, next = null) {
    this.val = val;
    this.next = next;
  }
}

l1 = new LinkedListNode(1);
l1.next = new LinkedListNode(2);
reverseList(l1);


Enter fullscreen mode Exit fullscreen mode

看起来很简单,对吧?要反转整个链表,只需反转每个指针。如果1指针指向 A 2,则将其翻转,2使其指向1B。



// 17 -> 2 -> 21 -> 6 -> 42 -> 10
// becomes
// 17 <- 2 <- 21 <- 6 <- 42 <- 10


Enter fullscreen mode Exit fullscreen mode

实际的逆向推导方法其实相当简单直接,但要注意的是,它需要一些时间来理清思路。很容易迷失方向,所以一定要画很多图表。

由于这是一个可以分解为子问题(反转整个链表)的问题(反转两个节点之间的指针),因此这似乎是一个很好的使用递归的机会。

实现实际逆操作的方法有很多种,我们将介绍迭代法递归法,但总体方法如下:

  1. 首先创建 3 个指针:newHeadheadnextNode
    1. newHeadnextNode初始化为null
    2. head开始时指向链表的头部。
  2. 重复(或递归地)以下步骤,直到head达到 n=1 null。这意味着已到达列表末尾:


class LinkedListNode {
  constructor(val, next = null) {
    this.val = val;
    this.next = next;
  }
}

l1 = new LinkedListNode(1);
l2 = new LinkedListNode(2);
l1.next = l2;

// we start at head
let head = l1;
let newHead = null;
while (head != null) {
  // store the node to the right to reuse later
  let nextNode = head.next;
  // set the current node's next to point backwards 
  head.next = newHead;
  // store the current node, to be used as the new next later
  newHead = head;
  // the previously right-side node is now processed
  head = nextNode;
}

console.log(l2);


Enter fullscreen mode Exit fullscreen mode

这一系列事件很难用图像来呈现,所以我们用评论来形象化。面试时,尽量不要一直想着这件事

在与面试官交谈的同时保持冷静和理清思路,会尤其困难。要充分利用白板,不仅用来记录内容,还要用来思考可能的步骤。

让我们一步一步来,然后再看一下运行的代码。我们先来反转一个非常简单的列表,比如 `[] 8 -> 4`。第一行是 `[]` let nextNode = head.next;,它会存储右边的节点。



nextNode = 4
// 8 -> 4


Enter fullscreen mode Exit fullscreen mode

然后我们将执行head.next = newHead;,这将使当前节点的next指向向后。



nextNode = 4
// <- 8, 4


Enter fullscreen mode Exit fullscreen mode

现在newHead = head;将存储当前节点,以便稍后用作新的下一个节点。



newHead = 8
nextNode = 4
// <- 8, 4


Enter fullscreen mode Exit fullscreen mode

最后,之前位于右侧的节点现在被处理:



newHead = 8
nextNode = 4
// <- 8, 4
         ^
   current node


Enter fullscreen mode Exit fullscreen mode

现在我们用同样的步骤处理下一个节点,nextNode = head.next;并将右侧的节点存储起来。



newHead = 8
nextNode = null
// <- 8, 4
         ^
   current node


Enter fullscreen mode Exit fullscreen mode

再次,将当前节点的next指针指向后方head.next = newHead;。记住,这newHead8!这就是我们进行切换的地方:



newHead = 8
nextNode = null
// <- 8 <- 4
           ^
     current node


Enter fullscreen mode Exit fullscreen mode

现在让我们看看如何用代码实现这一切,并附上大量的注释以供参考!



class LinkedListNode {
  constructor(val, next = null) {
    this.val = val;
    this.next = next;
  }
}

l1 = new LinkedListNode(8);
l2 = new LinkedListNode(4);
l1.next = l2;

// start at head, 8
let head = l1;
// example: 8 -> 4
let newHead = null;
while (head) {
  /* FIRST PASS */
  // store the node to the right
  let nextNode = head.next;
  // nextNode = 4, still 8 -> 4
  // set the current node's next to point backwards
  head.next = newHead;
  // 8 -> null
  // store the current node, to be used as the new next later
  newHead = head;
  // newHead = 8
  // the previously right-side node is now processed
  head = nextNode;
  // head = 4

  /* SECOND PASS */
  // store the node to the right
  nextNode = head.next;
  // nextNode = null
  // set the current node's next to point backwards
  head.next = newHead;
  // 4 -> 8
  // store the current node as the previous one
  newHead = head;
  // the previously right-side node is now processed
  head = nextNode;
}

console.log(l2);


Enter fullscreen mode Exit fullscreen mode

这样说都明白了吗?一定要反复尝试几次这种迭代方法。

以下是递归实现方法。这种方法乍一看可能有点棘手,但要知道,大部分的神奇之处都发生在最后。



function reverseList(head) {
  if (!head || !head.next) {
    return head;
  }

  let rest = reverseList(head.next);

  head.next.next = head;
  delete head.next;
  return rest;
}


Enter fullscreen mode Exit fullscreen mode

让我们再举一个简单的例子,8 -> 4看看它let rest = reverseList(head.next);如何运作4和调用reverseList

reverseList这样4会让我们触及终止条款,因为没有.next



if (!head || !head.next) {
  return head;
}


Enter fullscreen mode Exit fullscreen mode

我们向上追溯堆栈,回到8处理数据时的状态。rest现在它指向的就是之前的状态4。现在请注意发生了什么:



// remember, head is 8 - it is being processed
// head.next is 4
head.next.next = head;
// head.next.next was null since 4 wasn't pointing to anything
// but now head.next (4) points to 8


Enter fullscreen mode Exit fullscreen mode

我们返回4——它指向8。我们可以简单地将其推广到更长的链表!请注意,递归方法需要更多空间,因为我们需要维护调用栈。

文章来源:https://dev.to/jacobjzhang/a-visual-guide-to-reversing-a-linked-list-161e