链表 206.反转一个单链表

Posted by CodingWithAlice on June 3, 2021

链表 206/24.反转一个单链表

力扣 206反转一个单链表/剑指offer 24

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点

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

 //Definition for singly - linked list.
  function ListNode(val) {
    this.val = val;
    this.next = null;
  }

【小声BB】:虽然递归听起来比较高级,但是其实这三种算法都是一个时间复杂度的…所以能解决问题的算法就是好算法

  • 1.自己写的算法:

image-20210605160413474

时间复杂度:O(n),空间复杂度:xx

思想:用数组存储反向链表的值,然后再从链表头到尾重新赋值

  var reverseList = function (head) {
    // 存储第一个节点,用于获取节点值 arr
    let pNode = head;
    let arr = [];
    while (pNode !== null ) {
        arr.unshift(pNode.val);
        pNode = pNode.next
    };
    // 重新赋值第一个节点,作为结果的 head
    let first = head;
    for(let i=0; i<arr.length; i++) {
        head.val = arr[i];
        head = head.next;
    }
    return first
  };
  • 2.官方方法一:

假设存在链表 1 → 2 → 3 → Ø,我们想要把它改成 Ø ← 1 ← 2 ← 3。

通过三个节点迭代: prev 前一个节点,current 当前节点,next 下一个节点

image-20210605182813405

时间复杂度:O(n),空间复杂度:xx

var reverseList = function (head) {
    let prev = null;
    let current = head;
    while (current != null) {
      // 在更改引用之前,还需要另一个指针来存储原始的下一个节点
      let next = current.next;
      // 当前节点的 next 指针改为指向前一个元素(实现反向)
      current.next = prev;
      // (存的就是当前反向后的正确的节点)由于节点没有引用其上一个节点,事先存储其前一个元素
      prev = current;
      
      // 往后走到原始的下一个节点
      current = next;
    }
  	// 此时,由于 current 已经因为 while 循环走到了下一个 null 节点,所以返回 prev
    return prev
}

如下是上面算法的拆解版本,还是上面版本好,下面的算法可略过不看:

image-20210605173418297

时间复杂度:O(n),空间复杂度:xx

var reverseList = function (head) {
    // head 只有一个节点
    if (head === null || head.next === null) {
        return head
    }
    let second = head.next;

    // head 只有两个节点
    if (head.next.next === null) {
        second.next = head;
        head.next = null;
        return second;
    }
    let third = second.next;

    // 大于等于3个节点
    // tail 节点
    head.next = null;
    while(third !== null) {
        second.next = head;
        // 往后走
        head = second;
        second = third;
        third = third.next;
    }
    second.next = head;

    // 返回头节点
    return second

}
  • 3.官方方法二:递归

递归版本稍微复杂一些,其关键在于反向工作。

image-20210605190748794

时间复杂度:O(n),空间复杂度:xx

假设列表的其余部分已经被反转,现在我该如何反转它前面的部分?

假设列表为: n1->… -> n{k-1} -> n{k} -> n{k+1} -> … -> n{m} -> Ø

若从节点 n{k+1}到 n{m}已经被反转,而我们正处于 n{k}。

n{1}-> … -> n{k-1} -> n{k} -> n{k+1} <-… <-n{m}

我们希望 n{k+1} 的下一个节点指向 n{k} 。

所以,n{k}.next.next = n{k}。

边界条件(退出循环条件): n{1}的下一个必须指向 Ø 。

如果使用大小为 2 的链表测试代码,则可能会捕获此错误。

var reverseList = function (head) {
  if (head == null || head.next == null) return head;
  var p = reverseList(head.next);
  // 为了便于自己理解,假设输入的是 1->2->3->4->5:
  // 第一次返回的递归执行到这儿时:p = 5 -> null;head = 4->5(head由于闭包,每次都会比p早一个节点)
  head.next.next = head; // 5->4->5->4... 循环
  head.next = null; // 4 -> null
  // 返回 p 之前,可以看到:p = 5 -> 4 -> null
  return p;
}