每日算法随笔:反转链表

题解:反转链表

这道题目要求我们将一个单链表进行反转,返回反转后的链表。链表的反转可以通过 迭代递归 两种方法来实现。下面我们将详细解释这两种方法,并通过例子演示每一步的变化过程。

方法一:迭代法

思路

  • 我们用三个指针来完成链表的反转:prev 表示前一个节点,curr 表示当前节点,next 表示下一个节点。
  • 通过不断将当前节点的 next 指针指向 prev,实现链表的逐步反转。

迭代的步骤

  1. 初始化 prev = nullcurr = head,然后开始遍历链表。
  2. 在每次迭代中,先用 next 保存 curr.next,避免链表断开。
  3. curr.next 指向 prev,反转当前节点的指向。
  4. prev 移动到 curr,然后将 curr 移动到 next,继续下一次迭代。
  5. currnull 时,链表反转完成,prev 就是新的头节点。

代码实现

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next; // 保存下一个节点
            curr.next = prev; // 反转当前节点的指向
            prev = curr; // 将 prev 前移
            curr = next; // 将 curr 前移
        }
        return prev; // prev 成为新链表的头节点
    }
}

例子演示

输入head = [1,2,3,4,5]

  1. 初始状态:

    prev = null
    curr = 1 -> 2 -> 3 -> 4 -> 5
    
  2. 第一步:

    next = 2 -> 3 -> 4 -> 5
    curr.next = prev  // 1 -> null
    prev = 1 -> null
    curr = 2 -> 3 -> 4 -> 5
    
  3. 第二步:

    next = 3 -> 4 -> 5
    curr.next = prev  // 2 -> 1 -> null
    prev = 2 -> 1 -> null
    curr = 3 -> 4 -> 5
    
  4. 第三步:

    next = 4 -> 5
    curr.next = prev  // 3 -> 2 -> 1 -> null
    prev = 3 -> 2 -> 1 -> null
    curr = 4 -> 5
    
  5. 第四步:

    next = 5
    curr.next = prev  // 4 -> 3 -> 2 -> 1 -> null
    prev = 4 -> 3 -> 2 -> 1 -> null
    curr = 5
    
  6. 第五步:

    next = null
    curr.next = prev  // 5 -> 4 -> 3 -> 2 -> 1 -> null
    prev = 5 -> 4 -> 3 -> 2 -> 1 -> null
    curr = null
    

输出[5, 4, 3, 2, 1]

复杂度分析

  • 时间复杂度 :O(n),其中 n 是链表的节点数。我们只遍历了链表一次。
  • 空间复杂度:O(1),只用了常数级别的额外空间。

方法二:递归法

思路

  • 我们递归地反转链表的后续部分,直到最后一个节点成为新的头节点。
  • 每次递归返回时,将当前节点的下一个节点的 next 指向自己,同时将自己的 next 置为空,完成反转。

递归步骤

  1. 如果 headnull 或者 head.nextnull,直接返回 head 作为新的头节点(即递归的终止条件)。
  2. 递归反转剩余的链表。
  3. 将当前节点的下一个节点的 next 指向自己,同时将自己的 next 置为空。
  4. 返回新的头节点。

代码实现

java 复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head; // 递归终止条件
        ListNode newHead = reverseList(head.next); // 反转后续链表
        head.next.next = head; // 将后面的节点指向自己
        head.next = null; // 断开当前节点与后续的连接
        return newHead; // 返回新的头节点
    }
}

例子演示

输入head = [1,2,3,4,5]

  1. 初始递归:

    • reverseList(1) 递归调用 reverseList(2)
  2. 递归到最后一个节点:

    • reverseList(5) 返回 5 作为新头节点。
  3. 逐步反转链表:

    • reverseList(4)

      head = 4 -> 5
      反转后 5 -> 4
      head.next.next = 4
      head.next = null // 4 -> null
      返回 5 -> 4 -> null
      
    • reverseList(3)

      head = 3 -> 4 -> null
      反转后 5 -> 4 -> 3
      head.next.next = 3
      head.next = null
      返回 5 -> 4 -> 3 -> null
      
    • reverseList(2)

      head = 2 -> 3 -> null
      反转后 5 -> 4 -> 3 -> 2
      head.next.next = 2
      head.next = null
      返回 5 -> 4 -> 3 -> 2 -> null
      
    • reverseList(1)

      head = 1 -> 2 -> null
      反转后 5 -> 4 -> 3 -> 2 -> 1
      head.next.next = 1
      head.next = null
      返回 5 -> 4 -> 3 -> 2 -> 1 -> null
      

输出[5, 4, 3, 2, 1]

复杂度分析

  • 时间复杂度 :O(n),其中 n 是链表的节点数。递归过程中每个节点只处理一次。
  • 空间复杂度 :O(n),递归调用的栈深度为 n

总结:

  • 迭代法:通过三个指针逐步反转链表,时间和空间复杂度都为 O(n) 和 O(1),适合在空间要求较严格的场景下使用。
  • 递归法:利用函数调用栈进行递归,代码简洁直观,但需要 O(n) 的额外空间。