【数据结构和算法】3种方式解决

反转链表

http://www.nowcoder.com/questionTerminal/75e878df47f24fdc9dc3e400ec6058ca

1,使用栈解决

链表的反转是老生常谈的一个问题了,同时也是面试中常考的一道题。最简单的一种方式就是使用栈,因为栈是先进后出的。实现原理就是把链表节点一个个入栈,当全部入栈完之后再一个个出栈,出栈的时候在把出栈的结点串成一个新的链表。原理如下 图片说明

import java.util.Stack;
public class Solution {
public ListNode ReverseList(ListNode head) {
    Stack<listnode> stack= new Stack&lt;&gt;();
    //把链表节点全部摘掉放到栈中
    while (head != null) {
        stack.push(head);
        head = head.next;
    }
    if (stack.isEmpty())
        return null;
    ListNode node = stack.pop();
    ListNode dummy = node;
    //栈中的结点全部出栈,然后重新连成一个新的链表
    while (!stack.isEmpty()) {
        ListNode tempNode = stack.pop();
        node.next = tempNode;
        node = node.next;
    }
    //最后一个结点就是反转前的头结点,一定要让他的next
    //等于空,否则会构成环
    node.next = null;
    return dummy;
}
}

2,双链表求解

双链表求解是把原链表的结点一个个摘掉,每次摘掉的链表都让他成为新的链表的头结点,然后更新新链表。下面以链表1→2→3→4为例画个图来看下。 图片说明

图片说明 他每次访问的原链表节点都会成为新链表的头结点,最后再来看下代码

public ListNode ReverseList(ListNode head) {
    //新链表
    ListNode newHead = null;
    while (head != null) {
        //先保存访问的节点的下一个节点,保存起来
        //留着下一步访问的
        ListNode temp = head.next;
        //每次访问的原链表节点都会成为新链表的头结点,
        //其实就是把新链表挂到访问的原链表节点的
        //后面就行了
        head.next = newHead;
        //更新新链表
        newHead = head;
        //重新赋值,继续访问
        head = temp;
    }
    //返回新链表
    return newHead;
}

3,递归解决

我们再来回顾一下递归的模板,终止条件,递归调用,逻辑处理。

public ListNode reverseList(参数0) {
    if (终止条件)
        return;

    逻辑处理(可能有,也可能没有,具体问题具体分析)

    //递归调用
    ListNode reverse = reverseList(参数1);

    逻辑处理(可能有,也可能没有,具体问题具体分析)
}

终止条件就是链表为空,或者是链表没有尾结点的时候,直接返回


if (head == null || head.next == null)
    return head;

递归调用是要从当前节点的下一个结点开始递归。逻辑处理这块是要把当前节点挂到递归之后的链表的末尾,看下代码

public ListNode ReverseList(ListNode head) {
    //终止条件
    if (head == null || head.next == null)
        return head;
    //保存当前节点的下一个结点
    ListNode next = head.next;
    //从当前节点的下一个结点开始递归调用
    ListNode reverse = ReverseList(next);
    //reverse是反转之后的链表,因为函数reverseList
    // 表示的是对链表的反转,所以反转完之后next肯定
    // 是链表reverse的尾结点,然后我们再把当前节点
    //head挂到next节点的后面就完成了链表的反转。
    next.next = head;
    //这里head相当于变成了尾结点,尾结点都是为空的,
    //否则会构成环
    head.next = null;
    return reverse;
}

因为递归调用之后head.next节点就会成为reverse节点的尾结点,我们可以直接让head.next.next = head;,这样代码会更简洁一些,看下代码

public ListNode ReverseList(ListNode head) {
    if (head == null || head.next == null)
        return head;
    ListNode reverse = ReverseList(head.next);
    head.next.next = head;
    head.next = null;
    return reverse;
}

这种递归往下传递的时候基本上没有逻辑处理,当往回反弹的时候才开始处理,也就是从链表的尾端往前开始处理的。我们还可以再来改一下,在链表递归的时候从前往后处理,处理完之后直接返回递归的结果,这就是所谓的尾递归,这种运行效率要比上一种好很多

public ListNode ReverseList(ListNode head) {
    return reverseListInt(head, null);
}

private ListNode reverseListInt(ListNode head, ListNode newHead) {
    if (head == null)
        return newHead;
    ListNode next = head.next;
    head.next = newHead;
    return reverseListInt(next, head);
}

尾递归虽然也会不停的压栈,但由于最后返回的是递归函数的值,所以在返回的时候都会一次性出栈,不会一个个出栈这么慢。但如果我们再来改一下,像下面代码这样又会一个个出栈了

public ListNode ReverseList(ListNode head) {
    return reverseListInt(head, null);
}

private ListNode reverseListInt(ListNode head, ListNode newHead) {
    if (head == null)
        return newHead;
    ListNode next = head.next;
    head.next = newHead;
    ListNode node = reverseListInt(next, head);
    return node;
}

如果觉得有用就给个赞吧,还可以关注我的《牛客博客》查看更多的详细题解

数据结构和算法 文章被收录于专栏

专注于算法题的讲解,包含常见数据结构,排序,查找,动态规划,回溯算法,贪心算法,双指针,BFS和DFS等等。

全部评论
尾递归的: ListNode next = head.next;第一个自己定义的next这个节点作用是 head.next = newHead; newHead是指新定义的一个空链表么,
点赞 回复 分享
发布于 2021-04-01 21:40
空间复杂度为1!!!!!!递归和栈的空间复杂度是1吗??????
26 回复 分享
发布于 2022-01-20 18:05
这个用递归太难了,java想了半天没想通
13 回复 分享
发布于 2021-08-09 11:28
成环:答主的实现方法是将链表存入栈中,所以栈的每一个元素都是一个链表,而不是链表的某一个节点,所以会出现成环的情况。 如果你将链表的值(val)存入栈中,就不会有这种现象。
6 回复 分享
发布于 2022-05-11 00:09
用栈的思路和我一样,只要看到这种反转,就想到了栈,先进后出,正好符合要求
6 回复 分享
发布于 2021-11-04 20:26
要求空间复杂度是O(1),你根本就没完成题目要求。
3 回复 分享
发布于 2022-09-27 16:52 重庆
非常不错 我是刘静怡 我为自己代言
2 回复 分享
发布于 2022-11-23 18:29 广东
用栈空间复杂度就不是o(1)了
2 回复 分享
发布于 2022-07-15 22:06
其实只有头插法能O(1)空间
1 回复 分享
发布于 2023-10-04 00:58 陕西
尾递归private ListNode reverseListInt(ListNode head, ListNode newHead) { if (head == null) return newHead; ListNode next = head.next; head.next = newHead; return reverseListInt(next, head); } 里面的head.next = newHead 是把进了递归栈的head节点的next指针指到上一个node
1 回复 分享
发布于 2022-06-12 09:43
您好,请问为什么成环?使用栈方法
1 回复 分享
发布于 2021-05-07 16:10
递归可以好理解的讲讲吗
点赞 回复 分享
发布于 2024-08-14 17:53 广东
执行之后自测是有问题的,你定义的ListNode tempNode 和后面的 node和stack.pop()取出来的数据不是一个类型的,无法执行。
点赞 回复 分享
发布于 2023-12-06 23:25 上海
感觉一般递归就是憋着劲,到最后返回时再一股脑子处理,而尾递归感觉和 while或 for 循环的思想类似,有点正向处理的意思。
点赞 回复 分享
发布于 2023-11-04 13:29 上海
1
点赞 回复 分享
发布于 2023-04-26 16:01 内蒙古
全是空间O(n).....这么多赞?
点赞 回复 分享
发布于 2023-03-07 15:01 广东
O(1)时间复杂度欸
点赞 回复 分享
发布于 2023-03-03 17:00 日本
//最后一个结点就是反转前的头结点,一定要让他的next //等于空,否则会构成环 node.next = null; return dummy; ————————————————栈里最后一个节点应该是反转链表的尾节点吧
点赞 回复 分享
发布于 2022-10-19 03:05 安徽
想问下ListNode node = stack.pop();ListNode dummy = node;这里dummy对象是新建了一个对象引用node还是怎么回事,下面node的内容改变了,dummy也会随着改变吗?
点赞 回复 分享
发布于 2022-09-17 14:53 贵州
第一种栈的方式空间复杂度感觉不是O(1) 个人理解:泛是定义变量、对象引用、创建对象都会占用内存 while (!stack.isEmpty()) { ListNode tempNode = stack.pop(); node.next = tempNode; node = node.next; } 针对上面代码个人认为会受输入规模n的变化,内存的占用也发生变化,因为循环多次,导致声明了多个对象的引用。 不知理解准不准确?????????????????
点赞 回复 分享
发布于 2022-05-27 11:53

相关推荐

练习JAVA时长两年半:qps 30000
点赞 评论 收藏
分享
04-18 15:58
已编辑
门头沟学院 设计
kaoyu:这一看就不是计算机的,怎么还有个排斥洗碗?
点赞 评论 收藏
分享
评论
846
185
分享

创作者周榜

更多
牛客网
牛客企业服务