Java面试题之单链表反转

引言

这是一道很经典的“单链表逆序”问题。我去面试曾经碰到过两次。有一个公司明确题目要求不能使用额外的节点存储空间,有一个没有明确说明,但是都需要写出来数据结构。那时候也都是死记硬背的网上的答案,现在回顾一下。
参考博文:

死记硬背(内容提取)

此项适合复习时间短,需要回报大的童鞋,如需理解,请继续全篇查看本文章。

public static void main(String[] args) {
        Node a = new Node(1); 
        Node b = new Node(2);
        Node c = new Node(3);
        Node d = new Node(4);
        Node e = new Node(5);
        a.setNext(b);
        b.setNext(c);
        c.setNext(d);
        d.setNext(e);
        System.out.println(a);/*data=1-->data=2-->data=3-->data=4-->data=5-->null*/
        Node reverse = reverse(a);/*递归方式反转*/
        System.out.println(reverse);/*data=5-->data=4-->data=3-->data=2-->data=1-->null*/
}
class Node{
    private Node next;
    private int data;
    public Node(int data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    @Override
    public String toString() {
        return "data=" + data + "-->"+next;
    }
}
private static Node reverse(Node head) {
        /*如果是空链或者只是单个节点的链表  将直接返回*/
        if(head == null || head.getNext() ==null) {
            return head;
        }
        Node reverse = reverse(head.getNext());/*找到了最后一个   也就是5   当前head为4  reverse为5*/
        head.getNext().setNext(head);/* 1-->2-->3-->4-->5   变为   5-->4  1-->2-->3-->4  此时4指向5  5 也指向4*/
        head.setNext(null);  /*4-->null    5-->4-->null  1-->2-->3-->4 */        
        return reverse;    /*返回5-->4-->null*/
}

什么是单链表

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。 ——–百度百科

先手写一个单链表的数据结构

class Node{
    private Node next;
    private int data;
    public Node(int data) {
        this.data = data;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
    @Override
    public String toString() {
        return "data=" + data + "-->"+next;
    }
}

面试题而已,就没写多复杂,想很全面的话可以参考LinkedList,其本身就是一个链表(JDK9中是一个双向链表),下面是他的节点类。

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

第一步:新建链表

先构造各个链表节点,再通过setNext将其链接起来

public static void main(String[] args) {
        Node a = new Node(1); 
        Node b = new Node(2);
        Node c = new Node(3);
        Node d = new Node(4);
        Node e = new Node(5);
        a.setNext(b);
        b.setNext(c);
        c.setNext(d);
        d.setNext(e);
        System.out.println(a);/*data=1-->data=2-->data=3-->data=4-->data=5-->null*/
        Node reverse = reverse(a);/*递归方式反转*/
        System.out.println(reverse);/*data=5-->data=4-->data=3-->data=2-->data=1-->null*/
        Node reverse2 = reverse2(reverse);/*非递归方式反转(将之前反转后的单链表反转回来)*/
        System.out.println(reverse2);/*data=1-->data=2-->data=3-->data=4-->data=5-->null*/
}

初始单链表如下图:

java单xiang链表 java单向链表反转面试_链表

第二步:反转链表

方法一:递归

递归的思想非常简单,就是从头结点找到尾节点,然后从尾节点往回走,再将当节点的下一个节点的next设置为当前节点。

private static Node reverse(Node head) {
        /*如果是空链或者只是单个节点的链表  将直接返回*/
        if(head == null || head.getNext() ==null) {
            return head;
        }
        Node reverse = reverse(head.getNext());/*找到了最后一个   也就是5   当前head为4  reverse为5*/
        head.getNext().setNext(head);/* 1-->2-->3-->4-->5   变为   5-->4  1-->2-->3-->4  此时4指向5  5 也指向4*/
        head.setNext(null);  /*4-->null    5-->4-->null  1-->2-->3-->4 */        
        return reverse;    /*返回5-->4-->null*/
}

程序运行流程如下:

一直递归,递归到尾节点,返回Node reverse5–>null

java单xiang链表 java单向链表反转面试_java单xiang链表_02


当前head为4,head.getNext().setNext(head); 4,5循环

java单xiang链表 java单向链表反转面试_java单xiang链表_03


head.setNext(null);

java单xiang链表 java单向链表反转面试_java单xiang链表_04


然后返回Node reverse 5–>4–>null

java单xiang链表 java单向链表反转面试_结点_05


//当前head为3 此时3的next为4 3–>4 变为 4–>3

java单xiang链表 java单向链表反转面试_链表_06


//3–>null

java单xiang链表 java单向链表反转面试_递归_07


//返回5–>4–>3–>null

java单xiang链表 java单向链表反转面试_java单xiang链表_08


//当前head为2 此时2的next为3 2–>3 变为 3–>2

java单xiang链表 java单向链表反转面试_链表_09


// 2–>null

java单xiang链表 java单向链表反转面试_结点_10


//返回5–>4–>3–>2–>null

java单xiang链表 java单向链表反转面试_结点_11


//当前head为1 此时1的next为2 1–>2 变为 2–>1

java单xiang链表 java单向链表反转面试_递归_12


//1–>null

java单xiang链表 java单向链表反转面试_链表_13


//反转完成,返回5–>4–>3–>2–>1–>null

java单xiang链表 java单向链表反转面试_链表_14

方法二:非递归(临时存储)

刚开始就将第二个节点的next置为第一个 通过临时值获取到旧的next 以此类推

public static Node reverse2(Node head) {  
        if (head == null)  
            return head;  
        Node pre = head;// 上一结点        data=5-->data=4-->data=3-->data=2-->data=1-->null
        Node cur = head.getNext();// 当前结点       data=4-->data=3-->data=2-->data=1-->null
        Node tmp;// 临时结点,用于保存当前结点的指针域(即下一结点)  
        while (cur != null) {// 当前结点为null,说明位于尾结点  
            tmp = cur.getNext();  //data=3-->data=2-->data=1-->null
            cur.setNext(pre);// 反转指针域的指向   data=4-->data=3-->data=2-->data=1-->null  data=4不再指向data3      data=4-->data=5  此时 5也指向4
            pre = cur;  //前节点为  data=4-->data=5
            cur = tmp;  //当前为data=3-->data=2-->data=1-->null

        }  
        // 最后将原链表的头节点的指针域置为null,还回新链表的头结点,即原链表的尾结点   data=1-->data=2-->data=3-->data=4-->data=5-->null
        head.setNext(null);  //否则5还是指向4  导致循环

        return pre;  
    }

程序运行流程如下:

开始 pre data=5–>data=4–>data=3–>data=2–>data=1–>null

java单xiang链表 java单向链表反转面试_结点_15


cur data=4–>data=3–>data=2–>data=1–>null

java单xiang链表 java单向链表反转面试_链表_16


temp data=3–>data=2–>data=1–>null

java单xiang链表 java单向链表反转面试_链表_17


cur.setNext(pre);

java单xiang链表 java单向链表反转面试_链表_18


//tmp —- data=2–>data=1–>null

//data=3不再指向data2 data=3–>data=4–>data=5

java单xiang链表 java单向链表反转面试_结点_19


//前节点为data=3–>data=4–>data=5

//当前节点为data=2–>data=1–>null

//tmp —- data=1–>null

//data=2不再指向data1 data=2–>data=3–>data=4–>data=5

java单xiang链表 java单向链表反转面试_链表_20


//前节点为data=2–>data=3–>data=4–>data=5

//当前节点为data=1–>null

//tmp —- null

//data=1不再指向null data=1–>data=2–>data=3–>data=4–>data=5

java单xiang链表 java单向链表反转面试_结点_21


//前节点为data=1–>data=2–>data=3–>data=4–>data=5

//当前节点为null

//跳出循环 head.setNext(null);

java单xiang链表 java单向链表反转面试_结点_22

总结

递归方法如果层数过深可能会引起栈溢出,而非递归方法是使用了临时变量来存储数据。面试如果没有太多要求的话建议使用递归,可以节约内存的使用。