206.反转链表

132 阅读8分钟

1.力扣题目链接

(opens new window)

题意:反转一个单链表。

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

--

1)双指针

// 双指针
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;
        ListNode temp = null;
        while (cur != null) {
            temp = cur.next;// 保存下一个节点
            cur.next = prev;
            prev = cur;
            cur = temp;
        }
        return prev;
    }
}
  1. 首先,定义了三个指针变量:

    • prev:用于存储反转后链表的头节点,初始值为 null,因为在开始时还没有反转后的链表。
    • cur:当前遍历到的节点,初始值为输入链表的头节点 head
    • temp:用于暂存下一个节点的引用,在修改当前节点的指向后,可以用它来找到下一个节点,以继续遍历。
  2. 进入循环 while (cur != null),这个循环会遍历原链表中的每一个节点。

  3. 在循环中,首先保存当前节点 cur 的下一个节点的引用,即 temp = cur.next。这是因为在修改当前节点 curnext 指针后,会失去对下一个节点的引用,所以需要在修改前保存。

  4. 接着,将当前节点 curnext 指针指向 prev,完成了反转操作,即 cur.next = prev。这样,原链表中的当前节点变成了反转链表中的头节点。

  5. 然后,更新 prev 为当前节点 cur,更新 cur 为下一个节点 temp,继续遍历下一个节点。

  6. 重复以上步骤,直到遍历完整个链表,最后返回 prev,即反转后链表的头节点。

这段代码采用了迭代的方式来反转链表,使用双指针 prevcur 来实现。这是一种常用的反转链表的方法,它能够在线性时间复杂度内完成链表的反转操作。

举例:

假设有一个链表,包含以下节点:1 -> 2 -> 3 -> 4 -> 5。

开始时,链表的头节点是1,然后我们调用 reverseList 函数对这个链表进行反转。下面是反转的步骤:

  1. 初始状态:

    • prev = null
    • cur = 1
    • temp = null
  2. 进入循环,执行第一次迭代:

    • 保存下一个节点的引用:temp = 2
    • 反转当前节点:cur.next = null,此时1成为反转链表的头节点。
    • 更新 prev 为当前节点:prev = 1
    • 更新 cur 为下一个节点:cur = 2

    此时,反转链表为:1 -> null,prev 指向1,cur 指向2。

  3. 第二次迭代:

    • 保存下一个节点的引用:temp = 3
    • 反转当前节点:cur.next = prev,变成 2 -> 1
    • 更新 prev 为当前节点:prev = 2
    • 更新 cur 为下一个节点:cur = 3

    此时,反转链表为:2 -> 1,prev 指向2,cur 指向3。

  4. 重复上述步骤,继续迭代,直到遍历完整个链表。

最终,反转链表的状态为:5 -> 4 -> 3 -> 2 -> 1,prev 指向5,cur 指向null。

整个链表已经成功反转。函数返回 prev,它指向反转链表的头节点5。原来的链表已经被颠倒成了5 -> 4 -> 3 -> 2 -> 1。

2)递归

class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(null, head);
    }

    private ListNode reverse(ListNode prev, ListNode cur) {
        if (cur == null) {
            return prev;
        }
        ListNode temp = null;
        temp = cur.next;// 先保存下一个节点
        cur.next = prev;// 反转
        // 更新prev、cur位置
        // prev = cur;
        // cur = temp;
        return reverse(cur, temp);
    }
}

这段 Java 代码实现了反转链表的功能,但它使用了递归的方式来实现。与之前的迭代方式相比,递归是一种更为简洁但可能会占用更多内存的方法。下面是对这段代码的解释:

  1. public ListNode reverseList(ListNode head) 是公共方法,用于启动反转链表的过程。它接受链表的头节点 head 作为输入,并通过调用私有递归函数 reverse 来实际执行反转操作。

  2. private ListNode reverse(ListNode prev, ListNode cur) 是私有递归函数。这个函数接受两个参数:

    • prev:表示已经反转部分链表的头节点。在初始调用时,传入的是 null,表示反转的链表初始为空。
    • cur:表示当前正在处理的节点。
  3. 在递归函数内部,首先检查当前节点 cur 是否为 null,如果是,说明已经遍历完了整个链表,可以返回已反转链表的头节点 prev

  4. 如果 cur 不是 null,则继续进行反转操作:

    • 保存当前节点 cur 的下一个节点的引用到 temp 变量中,以便在递归调用时使用。
    • 将当前节点 curnext 指针指向 prev,即将当前节点加入已反转链表的头部。
    • 然后,递归调用 reverse 函数,将 prev 更新为当前节点 cur,将 cur 更新为 temp(下一个节点的引用),继续反转下一个节点。
  5. 递归调用会一直执行,直到 cur 变为 null,此时递归结束,函数开始返回反转后链表的头节点 prev

这段代码的核心思想与之前的迭代版本相同,都是通过不断地将当前节点的 next 指向前一个节点来反转链表,只是使用递归方式来实现。同样,这会将原链表反转,最后返回反转后链表的头节点。

举例:

假设有一个链表,包含以下节点:1 -> 2 -> 3 -> 4 -> 5。

开始时,链表的头节点是1,然后我们调用 reverseList 函数对这个链表进行反转。下面是反转的步骤:

  1. 初始状态:

    • prev = null
    • cur = 1
  2. 第一次递归调用 reverse(prev, cur)

    • 由于 cur 不为 null,我们执行以下步骤:
      • 保存当前节点 cur 的下一个节点的引用到 temptemp = 2
      • 反转当前节点 cur,将其 next 指向 prevcur.next = null,此时1成为反转链表的头节点。
      • 更新 prev 为当前节点 curprev = 1
      • 更新 cur 为下一个节点 tempcur = 2
  3. 第二次递归调用 reverse(prev, cur)

    • 此时的状态为:

      • prev 指向1,cur 指向2
      • temp 指向3
    • 由于 cur 不为 null,我们执行以下步骤:

      • 保存当前节点 cur 的下一个节点的引用到 temptemp = 3
      • 反转当前节点 cur,将其 next 指向 prev,变成 2 -> 1。
      • 更新 prev 为当前节点 curprev = 2
      • 更新 cur 为下一个节点 tempcur = 3
  4. 重复上述递归调用的步骤,直到遍历完整个链表。

最终,反转链表的状态为:5 -> 4 -> 3 -> 2 -> 1,prev 指向5,cur 指向null。

整个链表已经成功反转。函数返回 prev,它指向反转链表的头节点5。原来的链表已经被颠倒成了5 -> 4 -> 3 -> 2 -> 1。

3)从后向前递归

// 从后向前递归
class Solution {
    ListNode reverseList(ListNode head) {
        // 边缘条件判断
        if(head == null) return null;
        if (head.next == null) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode last = reverseList(head.next);
        // 翻转头节点与第二个节点的指向
        head.next.next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head.next = null;
        return last;
    } 
}

这段代码是一个递归方式实现的链表反转函数,其思路是从后向前递归处理链表节点。下面是对这段代码的解释:

  1. ListNode reverseList(ListNode head) 是一个公共方法,用于启动链表反转的过程。它接受链表的头节点 head 作为输入参数。

  2. 首先,进行一些边缘条件判断:

    • 如果输入的头节点 headnull,即链表为空,直接返回 null,因为反转一个空链表还是空链表。
    • 如果头节点的下一个节点 head.nextnull,即链表只有一个节点,直接返回头节点 head,因为反转一个只有一个节点的链表还是它自己。
  3. 如果链表包含多个节点,就进行递归调用。递归调用的参数是链表的头节点的下一个节点,即 reverseList(head.next)。这一步是为了翻转链表中除了头节点以外的部分。

  4. 在递归调用返回后,last 变量将包含已翻转的链表的头节点。此时,链表的头节点是原链表中的尾节点。

  5. 然后,将头节点 head 和尾节点 last 进行翻转操作:

    • head.next.next = head:这一步将尾节点 last 的下一个节点指向头节点 head,实现了翻转。
    • head.next = null:将头节点的 next 指针设置为 null,因为它变成了新的尾节点。
  6. 最后,函数返回 last,它现在是反转后链表的新头节点,而整个链表已经成功反转。

这段代码的关键点是使用递归来反转链表,并在递归调用的返回后执行头节点和尾节点的翻转操作,最终得到反转后的链表。这种递归方式同样能够成功地反转链表。

举例:

假设有一个链表,包含以下节点:1 -> 2 -> 3 -> 4 -> 5。

开始时,链表的头节点是1,然后我们调用 reverseList 函数对这个链表进行反转。下面是反转的步骤:

  1. 初始状态:

    • head = 1
    • head.next = 2
  2. 第一次递归调用 reverseList(head.next)

    • 进入递归,此时 head 是2,链表变成了2 -> 3 -> 4 -> 5。
    • 递归继续,head 是3,链表变成了3 -> 4 -> 5。
    • 递归继续,head 是4,链表变成了4 -> 5。
    • 递归继续,head 是5,链表变成了5。
    • 此时,head 是链表的尾节点5,满足递归的边缘条件,递归返回。
  3. 此时的 last 是5,即已经翻转后的链表的头节点。回到第一次递归调用的上下文:

    • head.next.next = head:将3 -> 4 -> 5 中的4的 next 指向3,翻转了3和4之间的连接,链表变成了2 -> 3 <- 4 <- 5。
    • head.next = null:将2的 next 指向 null,因为2成为了新的尾节点,链表变成了2 -> 3 <- 4 <- 5。
  4. 第一次递归调用返回,此时 last 是5,函数返回 last

最终,反转链表的状态为:5 -> 4 -> 3 -> 2 -> 1,last 指向5,即反转后链表的新头节点。原来的链表已经被颠倒成了5 -> 4 -> 3 -> 2 -> 1。