面试题:用 Java 逆序打印链表

 更新时间:2018年07月04日 10:29:56   作者:nanchen2251  
这篇文章主要介绍了面试题:用 Java 逆序打印链表,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

昨天的 Java 实现单例模式 中,我们的双重检验锁机制因为指令重排序问题而引入了 volatile 关键字,不少朋友问我,到底为啥要加 volatile 这个关键字呀,而它,到底又有什么神奇的作用呢?

volatile 这个关键字,在昨天的讲解中我们简单说了一下:被 volatile 修饰的共享变量,都会具有下面两个属性:

  • 保证不同线程对该变量操作的内存可见性。
  • 禁止指令重排序。

共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。

可见性:一个线程对共享变量值的修改,能够及时地被其它线程看到。

对于重排序,不熟悉的建议直接 Google 一下,这里也就不多提了。只需要记住,在多线程中操作一个共享变量的时候,一定要记住加上 volatile 修饰即可。

由于时间关系,我们还是得先进入今天的正题,对于 volatile 关键字,在要求并发编程能力的面试中还是很容易考察到的,后面我也会简单给大家讲解。

输入一个单链表的头结点,从尾到头打印出每个结点的值。

我们的链表有很多,单链表,双向链表,环链表等。这里是最普通的单链表模式,我们一般会在数据存储区域存放数据,然后有一个指针指向下一个结点。虽然 Java 中没有指针这个概念,但 Java 的引用恰如其分的填补了这个问题。

看到这道题,我们往往会很快反应到每个结点都有 next 属性,所以要从头到尾输出很简单。于是我们自然而然就会想到先用一个 while 循环取出所有的结点存放到数组中,然后再通过逆序遍历这个数组,即可实现逆序打印单链表的结点值。

我们假定结点的数据为 int 型的。实现代码如下:

public class Test05 {
  public static class Node {
    int data;
    Node next;
  }

  public static void printLinkReverse(Node head) {
    ArrayList<Node> nodes = new ArrayList<>();
    while (head != null) {
      nodes.add(head);
      head = head.next;
    }
    for (int i = nodes.size() - 1; i >= 0; i--) {
      System.out.print(nodes.get(i).data + " ");
    }
  }

  public static void main(String[] args) {
    Node head = new Node();
    head.data = 1;
    head.next = new Node();
    head.next.data = 2;
    head.next.next = new Node();
    head.next.next.data = 3;
    head.next.next.next = new Node();
    head.next.next.next.data = 4;
    head.next.next.next.next = new Node();
    head.next.next.next.next.data = 5;
    printLinkReverse(head);
  }
}

这样的方式确实能实现逆序打印链表的数据,但明显用了整整两次循环,时间复杂度为 O(n²)。等等!逆序输出?似乎有这样一个数据结构可以完美解决这个问题,这个数据结构就是栈。

栈是一种「后进先出」的数据结构,用栈的原理更好能达到我们的要求,于是实现代码如下:

public class Test05 {
  public static class Node {
    int data;
    Node next;
  }

  public static void printLinkReverse(Node head) {
    Stack<Node> stack = new Stack<>();
    while (head != null) {
      stack.push(head);
      head = head.next;
    }
    while (!stack.isEmpty()) {
      System.out.print(stack.pop().data + " ");
    }
  }

  public static void main(String[] args) {
    Node head = new Node();
    head.data = 1;
    head.next = new Node();
    head.next.data = 2;
    head.next.next = new Node();
    head.next.next.data = 3;
    head.next.next.next = new Node();
    head.next.next.next.data = 4;
    head.next.next.next.next = new Node();
    head.next.next.next.next.data = 5;
    printLinkReverse(head);
  }
}

既然可以用栈来实现,我们也极容易想到递归也能解决这个问题,因为递归本质上也就是一个栈结构。要实现逆序输出链表,我们每访问一个结点的时候,我们先递归输出它后面的结点,再输出该结点本身,这样链表的输出结果自然也是反过来了。

代码如下:

public class Test05 {
  public static class Node {
    int data;
    Node next;
  }

  public static void printLinkReverse(Node head) {
    if (head != null) {
      printLinkReverse(head.next);
      System.out.print(head.data+" ");
    }
  }

  public static void main(String[] args) {
    Node head = new Node();
    head.data = 1;
    head.next = new Node();
    head.next.data = 2;
    head.next.next = new Node();
    head.next.next.data = 3;
    head.next.next.next = new Node();
    head.next.next.next.data = 4;
    head.next.next.next.next = new Node();
    head.next.next.next.next.data = 5;
    printLinkReverse(head);
  }
}

虽然递归代码看起来确实很整洁,但有个问题:当链表非常长的时候,一定会导致函数调用的层级很深,从而有可能导致函数调用栈溢出。所以显示用栈基于循环实现的代码,健壮性还是要好一些的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java实现图片倒影的源码实例内容

    Java实现图片倒影的源码实例内容

    在本篇文章里小编给大家整理的是关于Java实现图片倒影的源码以及相关知识点,有需要的朋友们学习下。
    2019-09-09
  • 解决maven加载依赖时遇到的问题

    解决maven加载依赖时遇到的问题

    这篇文章主要介绍了解决maven加载依赖时遇到的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • SpringBoot应用监控Actuator使用隐患及解决方案

    SpringBoot应用监控Actuator使用隐患及解决方案

    SpringBoot的Actuator 模块提供了生产级别的功能,比如健康检查,审计,指标收集,HTTP 跟踪等,帮助我们监控和管理Spring Boot 应用,本文将给大家介绍SpringBoot应用监控Actuator使用隐患及解决方案,需要的朋友可以参考下
    2024-07-07
  • MyBatis查询 、修改 、删除操作示例代码

    MyBatis查询 、修改 、删除操作示例代码

    MyBatis 作为一款灵活的持久层框架,提供了直接编写 SQL 语句的能力,避免了其他 ORM 框架可能带来的性能和功能限制,本文介绍 MyBatis 中如何高效执行这三种操作,并通过代码示例展示最佳实践,感兴趣的朋友一起看看吧
    2024-08-08
  • Java中&&与?表达式结合时出现的坑

    Java中&&与?表达式结合时出现的坑

    这篇文章主要给大家介绍了关于Java中&&与?表达式结合时出现的坑的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-02-02
  • springsecurity基于token的认证方式

    springsecurity基于token的认证方式

    本文主要介绍了springsecurity基于token的认证方式,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • 深入理解Java8新特性之Lambda表达式的基本语法和自定义函数式接口

    深入理解Java8新特性之Lambda表达式的基本语法和自定义函数式接口

    Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑
    2021-11-11
  • Spring MVC框架配置方法详解

    Spring MVC框架配置方法详解

    这篇文章主要为大家详细介绍了Spring MVC框架的配置方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • Java将数组转换成字符串的四种方法总结

    Java将数组转换成字符串的四种方法总结

    这篇文章主要给大家介绍了关于Java将数组转换成字符串的四种方法,每种方法都有其适用的场景和优缺点,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-12-12
  • springboot的logback配置源码解读

    springboot的logback配置源码解读

    这篇文章主要为大家介绍了springboot的logback配置,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11

最新评论