剑指offer | 面试题24:反转链表_java

开始行动,你已经成功一半了,献给正在奋斗的我们

 

题目

定义一个函数,输入一个链表的头节点,反转该链表并输出反转过后的链表的头节点。

链表的节点定义如下:

public class ListNode {

    // 链表值
    public int val;
    // 下一节点的引用
    public ListNode next;

    // 构造函数
    public ListNode(int x) {
        val = x;
    }
}

解题分析

首先,我们要明确链表要怎么删除和新增一个元素;
单向链表是不能够自我删除的,因为单向链表除了删除头节点,删除的节点的方法只有一个就是将这个节点的前一个节点的引用指向这个节点的下一个节点;

剑指offer | 面试题24:反转链表_算法_02

(图片来源于网络)

就比如说,你是中间的小男孩,牵着两个女孩的手,那么他们要怎么放弃你呢,就是你左边的女孩牵起来你右边女孩的手,你就不再这个小团体里面了。

那这个小男孩又要怎么进来了,那就是分四步:
1) 左边的女孩放开右边的女孩的手
2) 左边的女孩牵起这个小男孩的手
3)小男孩牵起右边女孩的手
如果要是有个小男孩想站在最左边的女孩旁边呢?那就简单了,就一步,牵起最左边女孩的手即可。

同理,向链表的头添加元素,只需要把新元素的下一节点的引用指向原来的头节点即可。
向链表的中间添加元素,需要前一节点的下一节点引用指向要添加的元素,要添加的元素的下一节点引用指向原来下一节点的引用。

那我们要怎么让去反转一个链表呢?
我们可以依次遍历链表,把链表的元素一个一个的拆下来,依次依次的放到新链表里面,先来的节点都是原来靠前的,既然要反转,就需要将它放到后面。这样先来的放到后面,最后来的就成了第一个节点,整个链表就反转了。

比如我们有如下的链表,链表中共有四个元素;
剑指offer | 面试题24:反转链表_数据结构_03
我们先把节点1拆下来;

剑指offer | 面试题24:反转链表_java_04
如果这里节点1是个null,我们很容易空指针,并且不容易操作,所以我们一般会建一个虚拟节点,让第一个元素下来就能被挂在虚拟节点的身后,这样就算头节点是null。最后返回结果的时候返回的是虚拟节点的下一个节点,定多返回null,不是空指针。

把节点1挂在虚拟节点上面;
剑指offer | 面试题24:反转链表_java_05
然后把节点2拆下来,让虚拟节点的下一个节点指向节点2,节点2的下一个节点指向节点1,这样前两个节点就被反转了;
剑指offer | 面试题24:反转链表_单链表_06
一直如上操作,最后把虚拟节点4挂在虚拟节点后面,节点3下一节点指向节点3;
剑指offer | 面试题24:反转链表_链表_07
最后不要虚拟节点,直接返回虚拟节点的下一个节点;
剑指offer | 面试题24:反转链表_java_08
即反转后的链表;

原理很容易懂,但是切记,懂了和能写出来是两码事;
编码中首先要注意代码的健壮性,当然你使用了虚拟节点,不太可能空指针。
其次,把一个拆下来的节点放到新链表这块的操作很绕,因为你需要记得每一个对象的下一个引用都指向谁了,一不小心很容易出现链表断裂和死循环链表;

我的建议是,尽量理解,不理解也没关系,我们只要记住,一共四步;(顺序不错,一个都不能错)
1) 记录下来你要拆下来的节点的下一节点引用对象(第一步一定是这个,因为你不记住这个对象,你讲找不到你要遍历的下一个节点)
2)原下一个节点已经记住了,那么就把拆下来的节点的下一个节点换成虚拟节点的下一个节点(就是原来老大变成现在的老二的过程)
3)把虚拟头节点的下一个节点换成拆下来的节点(新老大上位)
4)遍历的指针由拆下来的节点编程1)中保留的拆下来的节点的原来的下一个节点

代码(JAVA实现)

ps:这里笔者使用的jdk为1.8版本

public class Num206_ReverseList {


    public static void main(String[] args) {

        LinkedListUtil.printLinkedList(reverseList(LinkedListUtil.getLinkedList(new int[]{1, 2, 3, 4, 5, 6})));
    }

    public static ListNode reverseList(ListNode first) {
        ListNode point = first;
        ListNode virtual = new ListNode(-1);

        ListNode temp;
        while (Objects.nonNull(point)) {
            temp = point.next;
            point.next = virtual.next;
            virtual.next = point;
            point = temp;
        }

        return virtual.next;
    }
}

LinkedListUtil为笔者自己定义方便做链表题目的工具类,代码如下:


/**
 * @program: algorithm_code
 * @description: 链表工具类
 * @author: YangHang
 * @create: 2019-09-01 21:56
 **/

public class LinkedListUtil {

    /**
     * 获取一个链表
     */
    public static ListNode getLinkedList(int[] numbers) {

        if (numbers == null || numbers.length == 0) {
            return null;
        }

        ListNode first = new ListNode(numbers[0]);
        ListNode intermediateVariables = first;

        for (int i = 1; i < numbers.length; i++) {
            ListNode temp = new ListNode(numbers[i]);
            intermediateVariables.next = temp;
            intermediateVariables = intermediateVariables.next;
        }

        return first;
    }

    /**
     * 打印一个链表
     */
    public static void printLinkedList(ListNode first) {
        ListNode intermediateVariables = first;

        while (!Objects.isNull(intermediateVariables)) {

            System.out.printf("%s-> ", intermediateVariables.val);
            intermediateVariables = intermediateVariables.next;
        }
        System.out.println();

    }

    // 测试工具类
    public static void main(String[] args) {
        printLinkedList(getLinkedList(new int[]{1, 2, 3, 4, 5, 6, 7}));
    }
}
剑指offer | 面试题24:反转链表_链表_09
喜欢的朋友可以加我的个人微信,我们一起进步