单链表反转思路&实现&错误复盘(JAVA)

本文详细解析了Java中单链表反转的实现过程,探讨了在反转操作中遇到的引用问题和边界条件处理。通过实例分析了错误的代码实现,指出在方法参数传递中的值传递特性,并给出了修正后的正确代码。文章强调了对基础概念的掌握对于解决问题的重要性,并提供了反转链表的O(n)时间复杂度、O(1)空间复杂度的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天想练习下单链表反转, 实现思路不难, 没想到写代码出现这么多问题
还是基础不扎实, 必须记录问题复盘一下!

构建一个单链表类

构建一个单链表的节点类, 单链表需要有:

  1. 当前节点的值 (这是使用字符Character, 为了方便我通过字符串来构建一个字符链表)
  2. 指向下一个节点的引用
public class Node {

    private Node next;

    private Character value;

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public Character getValue() {
        return value;
    }


    public Node() {
    }

    public Node(Character value) {
        this.value = value;
    }

    /**
     * 根据字符串构造单向链表
     *
     * @param str
     */
    public static Node build(String str) {
        Node next = null;
        for (int i = str.length() - 1; i >= 0; i--) {
            Node node = new Node(str.charAt(i));
            node.setNext(next);
            next = node;
        }
        return next;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Node{");
        sb.append("value=").append(value);
        sb.append(", next=").append(next);
        sb.append('}');
        return sb.toString();
    }
}

反转代码实现

这里整理下思路: 单链表反转需要记录三个指针, last->item->next,
每次反转last<-item后, 把last,item.next整体前移一位, 直至next=null, 到达链表的末端

1. 第一次错误的的实现

第一次代码实现: 这里我犯了几个大错误! 也是对Java的引用理解不到位容易出现的错误!
有兴趣可以看下我这段代码的问题, 或者直接拉倒最后看正确的。

    public static void reverse(Node node){
        //单链表反转需要记录三个指针, last->item->next,
        // 反转last<-item后, 把last,item.next整体前移一位

        Node first = new Node();
        first.setNext(node);

        Node last = first;
        Node item = node;
        Node next = node.getNext();

        while(next != null){
            //反转
            item.setNext(last);
            //前移
            last = item;
            item = next;
            next = next.getNext();
        }

        //处理尾部边界问题 last->item->next(null)
        item.setNext(last);
        node = item;

        //还原边界
        first = null;
    }

存在的第一个问题

首先第一个问题, 如果你调用上面的方法, 并打印node, 将会导致内存溢出
原因在于在第一次反转后, first->node的引用没有移除, 而反转后, node的next变成了first
也就是first->next, next->first, 形成了死环
错误出现在打印时的toString()方法, 形成死循环, 导致内存溢出。

实际上边界节点first不需要指向node, 因为我们已经有item记录了首节点node的地址
所以我的第一次修改是, 而且后面还要还原边界节点 first = null;

//first.setNext(node);
first.setNext(null);

实际上这又犯了引用的第二个错误
因为first只是对new Node()的一个引用, 当第一次反转

 Node last = first;
  item.setNext(last);

item的next指向的是first实际记录的Node对象的地址值, 而不是first的值,
first->new Node()
item.next->first->new Node()
item.next->new Node()

我修改first=null, 并不能修改item.next的引用
first->null
item.next->new Node()

实际上边界节点可以直接是null

//初始化时首节点为边界null
        Node last = null;
        Node item = node;
        Node next = node.getNext();

存在的第二个问题

第二个问题, 还是引用的问题
Java方法的参数传递是值传递, 这句话可能听过很多遍, 但一直没有真正的理解!

我一开始的想法是: Node是我定义的class, 所以这个反转的方法不需要返回值, 直接修改Node的值即可, 这样的思路其实没问题, 因为方法形参传递的是Node的引用, 即对象在堆中的地址值。问题出在我代码的最后一行

        node = item;

这里item指向堆中的Node对象, 也就是原来链表的尾节点。
item->Node(null)
node->item->Node(null)
node->Node(null)

那么方法的调用方能不能访问这个Node(null)呢, 答案是不能!

//方法调用方
    public static void main(String[] args) {
        Node abcdefg = Node.build("abcdefg");
        reverse(abcdefg);
        System.out.println(reverse);
    }
    
//栈中的方法
    public static void reverse(Node node){
        ...
        node = item;
    }

方法在调用时, 参数的传递是值传递, 方法压栈的时候, 在栈中创建了局部变量node, 调用方把 abcdefg的值也就是 Node(“abcdefg”) 对象在堆内存中的地址值传递给了局部变量node, 此时:
abcdefg->Node(“abcdefg”)
node->abcdefg->Node(“abcdefg”)

如果我们在方法体中把item赋值给node, 则有
abcdefg->Node(“abcdefg”)
node->item->Node(null)

但要注意, 此时的abcdefg还是指向链表原来的首节点Node(“abcdefg”), 我们并不能通过这种方式在调用reverse方法后直接访问到链表的尾节点,
我们只能访问到首节点值的修改, 这里首节点的next引用已经从Node(“b”)变成了Node(null), 因为这些是在堆内存中完成的。

修改后可行的代码

    public static Node reverse(Node node){
        //单链表反转需要记录三个指针, last->item->next,
        // 反转last<-item后, 把last,item,next整体前移一位

        // 带头哨兵解决边界问题
        Node last = null;

        Node item = node;
        Node next = node.getNext();

        while(next != null){
            //反转
            item.setNext(last);
            //前移
            last = item;
            item = next;
            next = next.getNext();
        }

        //处理尾部边界问题 last->item->next(null)
        item.setNext(last);
        return item;

复杂度

时间复杂度O(n), 空间复杂度O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code tea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值