[路飞]_5分钟学会反转链表

491 阅读5分钟

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

先来看一个场景

  1. 打开力扣刷算法
  2. 一道链表简单题,信心满满,直接开始撸代码
  3. 反手一个提交,直接报错
  4. 然后开始找bug,找了老半天还是报错
  5. 最后心态就崩了,关闭力扣继续摸鱼。。。 以上是我一开始刷题碰到的情况,其实在做链表题的时候最好是通过画图来理清思路,否则很容易把自己绕晕。接下来看几道反转链表相关的算法题。

从简单题开始

206. 反转链表

图片.png

分析

  • 以题目给出的示例1为例,如果要得到反转后的链表那么1节点最终将指向null
  • 因此先新建一个指针p指向null
  • 再新建一个q指针指向head方便之后的反转操作

图片.png

  • 如果想将1节点指向null,不能直接修改q.next = p,这样将断开链表,失去了从2节点开始的引用。需要用一个变量去接收,因此将head向后移动拿到2节点的引用,head = head.next

图片.png

  • 现在就可以将1节点next指向nullq.next = p

图片.png

  • 然后将pq指针向后移动,p = qq = head,切忌赋值顺序不能反,这样就完成了1节点的反转 图片.png

  • 重复以上的步骤就能完成所有的反转,来看一下动图

反转链表.gif

  • 最后返回p指针就是反转后的链表

代码实现

/**
 * 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) {
    var p = null, q = head
    while(q) { // 顺序不能颠倒,否则将会失去引用
        head = head.next
        q.next = p
        p = q
        q = head
    }
    return p
};
  • 根据上面的图片分析就能很好写出代码,接下来增加一下难度

进阶中等题

92. 反转链表 II

图片.png

分析

  • 有了上一道题的经验,这道题只是多加了反转的位置和数量,不需要整个链表都反转,主要的焦点在于,如何将链表拆开部分反转,然后再连接回原链表
  • 示例1为例,首先定义一个虚拟头节点ret指向head,目的是方便返回,因为可能从头结点开始就反转,最终只需要返回ret.next即可

图片.png

  • 接着定义一个pre指针一开始指向ret,依次向后移动找到待反转区域的前一个节点,即1节点,然后操作他的下一个节点进行区域反转

图片.png

  • 为什么是找到前一个节点,而不是在待反转节点的第一个节点直接进行反转,如果pre指针指向待反转开始的2节点,区域反转后会变成4->3->2->5,此时1节点next依然指向2节点,最终的结果就会变成1->2->5,然而我们需要的是1节点next指向4->3->2->5

  • 2节点的位置进行操作,反转的步骤和上一题类似,不同的是需要加一个变量用于接收反转后的结果,一开始也是指向2节点 图片.png

  • 反转结束后将这个变量的next指向反转区域的下一个节点5 图片.png

  • 最后把这一个反转后的头节点p返回拼上原链表

  • 整体动画演示

反转链表II.gif

代码实现

/**
 * 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) {
  var ret = new ListNode(null, head)
  var cnt = right - left + 1 // 需要反转区间的个数
  var pre = ret
  while (--left) {
    pre = pre.next
  }
  pre.next = reverse(pre.next, cnt) // 必须反转p.next,如果直接反转p,p的前一个元素仍然指向p,中间反转的部分都被略过了
  return ret.next
};

function reverse(head, cnt) {
  var p = null,
    q = ret = head
  while (cnt--) {
    head = head.next
    q.next = p
    p = q
    q = head
  }
  ret.next = q // 原来的头节点已经指向了null,再连接上反转后的下一个元素
  return p
}

困难题也可迎刃而解

25. K 个一组翻转链表

图片.png

分析

  • 这道题其实就是上两道题的合体版本,如果你不知道如何反转链表,直接看这道题可能有思路,但是直接上手可能会定义一大堆变量,到最后把自己绕进去了
  • 92. 反转链表 II的基础上,需要判断是否符合反转的条件,如果剩下的节点数量小于待反转的数量k则不反转
  • 一开始依然是定义一个pre指针一开始指向head
  • 再定义一个指针pre一开始指向ret,每次在反转完k个节点后,向后移动k步,直到走到末尾或者剩余数量小于k
  • 最终返回ret.next即可

参考代码

/**
 * 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} k
 * @return {ListNode}
 */
var reverseKGroup = function (head, k) {
  if (k === 1) return head
  var ret = new ListNode(0, head)
  var pre = ret

  while (1) {
    pre.next = reverse(pre.next, k)
    var n = k
    while (n-- && pre) { // 反转完以后向后走
      pre = pre.next
    }
    if (!pre) break // 待反转数量不够跳出循环
  }

  return ret.next
};

function reverse(head, k) {
  var p = ret = q = head,
    n = k
  while (--n && p) {
    p = p.next
  }
  if (!p) return head //需要反转的节点数量小于k则不反转
  p = null
  while (k--) {
    head = head.next
    q.next = p
    p = q
    q = head
  }
  ret.next = q
  return p
}
  • 如果还不是很清楚,可以动手画一下

-- end --