算法-反转链表

发布于:2025-02-13 ⋅ 阅读:(10) ⋅ 点赞:(0)

反转链表应该就是把每个节点的指针方向反过来,对吧?比如说原本是1->2->3->4->null,反转之后变成4->3->2->1->null。

那怎么操作呢?可能需要用三个指针,或者两个指针。我记得之前学的时候有两种方法,迭代和递归。用户可能想知道这两种方法,不过可能先考虑迭代的方式,因为递归有时候可能不太好理解,或者栈溢出问题。

首先,迭代法的话,可能需要一个前驱节点prev,当前节点current,以及下一个节点next。每次把current的next指向prev,然后把prev和current往后移。比如,初始时prev是null,current是head。然后循环,保存current的下一个节点next,把current的next指向prev,然后prev移动到current,current移动到next。直到current为null,这时候prev就是新的头节点。

那这样的话,代码应该怎么写呢?比如在Java里,定义一个链表节点类,然后写反转的方法。比如:

public ListNode reverseList(ListNode head) {

ListNode prev = null;

ListNode current = head;

while (current != null) {

ListNode next = current.next;

current.next = prev;

prev = current;

current = next;

}

return prev;

}

这个应该是对的。那测试一下,比如例子1->2->3->4。初始prev是null,current是1。进入循环,next是2,current.next指向null,prev变成1,current变成2。下一次循环,next是3,current.next指向1,prev变成2,current变成3。依此类推,最后prev就是4,链表反转成功。

然后递归的方法呢?递归的话,可能要从最后一个节点开始反转。比如递归到最后一个节点,然后每次将当前节点的下一个节点的next指向自己,自己的next置为null。比如:

public ListNode reverseListRecursive(ListNode head) {

if (head == null || head.next == null) {

return head;

}

ListNode newHead = reverseListRecursive(head.next);

head.next.next = head;

head.next = null;

return newHead;

}

这样的话,假设原链表是1->2->3->4。递归到head=3的时候,head.next是4,递归返回4,这时候head.next.next = head,即4的next指向3,然后3的next设为null。然后回到上一层,处理2,这时候2的next是3,反转后3的next指向2,然后2的next设为null。最后处理1,同样的步骤,得到4->3->2->1。这样递归的方法也能实现。

用户可能想知道这两种方法的区别,或者哪一种更优。迭代法是O(n)时间,O(1)空间,而递归是O(n)时间,O(n)栈空间。所以如果链表很长的话,递归可能会栈溢出,这时候应该用迭代法更好。

另外,可能需要处理链表为空的情况,或者只有一个节点的情况,这时候直接返回头节点就可以了。比如在递归的终止条件里,如果head或者head.next是null,就返回head。这样是正确的。

用户有没有其他需求呢?比如是否需要解释每一步的过程,或者是否需要图示?或者是否需要处理循环链表的情况?不过题目应该假设链表是单向无环的。所以按照常规情况处理即可。

总结来说,反转链表的两种方法,迭代和递归,都是可行的,需要根据具体情况选择。代码实现起来也不算复杂,关键是要正确调整指针的指向,同时注意不要丢失节点的引用。

以下是反转链表的详细步骤和代码实现:

方法一:迭代法

思路:使用三个指针(前驱节点 prev、当前节点 curr、临时节点 next)逐个反转节点指向。

步骤

  1. 初始化 prev 为 nullcurr 为头节点 head

  2. 遍历链表,每次循环中:

    • 保存 curr 的下一个节点到 next

    • 将 curr 的 next 指向 prev

    • prev 和 curr 向后移动一位。

  3. 当遍历完成时,prev 即为新链表的头节点。

代码实现(Java)

java

复制

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

public 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 最终指向新头节点
    }
}

方法二:递归法

思路:递归到链表末端,从后往前逐个反转节点指向。

步骤

  1. 递归终止条件:当前节点为空或为最后一个节点,直接返回。

  2. 递归反转后续链表,得到新头节点 newHead

  3. 将当前节点的下一节点的 next 指向当前节点,再将当前节点的 next 置空。

  4. 最终返回新头节点 newHead

代码实现(Java)

java

复制

public class Solution {
    public ListNode reverseListRecursive(ListNode head) {
        if (head == null || head.next == null) {
            return head; // 终止条件:空节点或单节点
        }
        ListNode newHead = reverseListRecursive(head.next); // 递归反转后续链表
        head.next.next = head; // 反转指向
        head.next = null;       // 断开原指向
        return newHead;         // 返回新头节点
    }
}

示例演示

原始链表1 -> 2 -> 3 -> 4 -> null
反转过程(迭代法):

  1. 初始状态:prev = nullcurr = 1

  2. 第一次循环:curr.next 指向 null,链表变为 1 -> nullprev = 1curr = 2

  3. 第二次循环:curr.next 指向 1,链表变为 2 -> 1 -> nullprev = 2curr = 3

  4. 第三次循环:curr.next 指向 2,链表变为 3 -> 2 -> 1 -> nullprev = 3curr = 4

  5. 第四次循环:curr.next 指向 3,链表变为 4 -> 3 -> 2 -> 1 -> nullprev = 4curr = null

最终链表4 -> 3 -> 2 -> 1 -> null


复杂度分析

  • 时间复杂度O(n),需遍历所有节点。

  • 空间复杂度

    • 迭代法:O(1),仅使用常量额外空间。

    • 递归法:O(n),递归调用栈深度为链表长度。


方法对比

方法 优点 缺点 适用场景
迭代法 空间效率高(O(1)) 代码稍复杂 常规情况,长链表
递归法 代码简洁 栈空间开销(O(n)) 短链表,教学示例

根据实际需求选择合适方法即可。

可以用动图解释一下,迭代法吗

迭代法反转链表的动图演示

初始状态

复制

链表:1 -> 2 -> 3 -> 4 -> null
指针:
- prev = null
- curr = 1
第一次循环
  1. 保存 curr.next 到 next

    复制

    next = 2
  2. 反转 curr.next 指向 prev

    复制

    1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 1
    curr = 2

状态

复制

链表:1 -> null
部分反转链表:1 -> null
剩余链表:2 -> 3 -> 4 -> null
第二次循环
  1. 保存 curr.next 到 next

    复制

    next = 3
  2. 反转 curr.next 指向 prev

    复制

    2 -> 1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 2
    curr = 3

状态

复制

链表:2 -> 1 -> null
部分反转链表:2 -> 1 -> null
剩余链表:3 -> 4 -> null
第三次循环
  1. 保存 curr.next 到 next

    复制

    next = 4
  2. 反转 curr.next 指向 prev

    复制

    3 -> 2 -> 1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 3
    curr = 4

状态

复制

链表:3 -> 2 -> 1 -> null
部分反转链表:3 -> 2 -> 1 -> null
剩余链表:4 -> null
第四次循环
  1. 保存 curr.next 到 next

    复制

    next = null
  2. 反转 curr.next 指向 prev

    复制

    4 -> 3 -> 2 -> 1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 4
    curr = null

状态

复制

链表:4 -> 3 -> 2 -> 1 -> null
部分反转链表:4 -> 3 -> 2 -> 1 -> null
剩余链表:null
结束
  • curr 为 null,循环结束。

  • prev 指向新链表的头节点 4


动图演示

以下是迭代法反转链表的动图演示:

复制

初始链表:
1 -> 2 -> 3 -> 4 -> null

第一次循环:
1 -> null
2 -> 3 -> 4 -> null

第二次循环:
2 -> 1 -> null
3 -> 4 -> null

第三次循环:
3 -> 2 -> 1 -> null
4 -> null

第四次循环:
4 -> 3 -> 2 -> 1 -> null
null

最终链表:
4 -> 3 -> 2 -> 1 -> null

代码回顾

以下是迭代法的代码实现:

java

复制

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 最终指向新头节点
}

总结

  • 迭代法通过三个指针(prevcurrnext)逐个反转链表节点。

  • 每次循环中,反转 curr 的指向,并移动 prev 和 curr

  • 最终 prev 指向新链表的头节点。

通过动图和代码结合,可以更直观地理解迭代法反转链表的过程!

解释一下 curr.next = prev; // 反转指针

反转链表的迭代法中,curr.next = prev 是核心操作,它的作用是 反转当前节点的指针方向。下面详细解释这一操作的含义和作用。


1. 代码片段回顾

java

复制

while (curr != null) {
    ListNode next = curr.next; // 保存下一个节点
    curr.next = prev;         // 反转指针
    prev = curr;              // 移动 prev
    curr = next;              // 移动 curr
}

2. curr.next = prev 的作用

2.1 反转指针方向
  • 原始链表:每个节点的 next 指针指向下一个节点。

    复制

    1 -> 2 -> 3 -> 4 -> null
  • 反转操作:将当前节点 curr 的 next 指针指向 prev,即前一个节点。

    复制

    curr.next = prev;

    例如,当 curr 指向 2 时,prev 指向 1,执行 curr.next = prev 后:

    复制

    2 -> 1
2.2 断开原链表
  • 反转指针后,当前节点 curr 不再指向原来的下一个节点,而是指向前一个节点 prev

  • 这样,原链表被逐步断开,新链表逐步形成。


3. 具体步骤解析

初始状态

复制

链表:1 -> 2 -> 3 -> 4 -> null
指针:
- prev = null
- curr = 1
第一次循环
  1. 保存 curr.next 到 next

    复制

    next = 2
  2. 反转 curr.next 指向 prev

    复制

    curr.next = null

    链表变为:

    复制

    1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 1
    curr = 2
第二次循环
  1. 保存 curr.next 到 next

    复制

    next = 3
  2. 反转 curr.next 指向 prev

    复制

    curr.next = 1

    链表变为:

    复制

    2 -> 1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 2
    curr = 3
第三次循环
  1. 保存 curr.next 到 next

    复制

    next = 4
  2. 反转 curr.next 指向 prev

    复制

    curr.next = 2

    链表变为:

    复制

    3 -> 2 -> 1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 3
    curr = 4
第四次循环
  1. 保存 curr.next 到 next

    复制

    next = null
  2. 反转 curr.next 指向 prev

    复制

    curr.next = 3

    链表变为:

    复制

    4 -> 3 -> 2 -> 1 -> null
  3. 移动 prev 和 curr

    复制

    prev = 4
    curr = null

4. 动图演示

以下是 curr.next = prev 的动图演示:

复制

初始链表:
1 -> 2 -> 3 -> 4 -> null

第一次循环:
1 -> null
2 -> 3 -> 4 -> null

第二次循环:
2 -> 1 -> null
3 -> 4 -> null

第三次循环:
3 -> 2 -> 1 -> null
4 -> null

第四次循环:
4 -> 3 -> 2 -> 1 -> null
null

5. 总结

  • curr.next = prev 是反转链表的核心操作,它将当前节点的 next 指针指向前一个节点 prev

  • 通过这一操作,链表的方向被逐步反转,最终形成新的反转链表。

  • 结合 prev 和 curr 的移动,整个链表被逐个节点反转。