剑指Offer面试题06:从尾到头打印链表(四种方法)

题目:

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000

题目看起来是没什么难度的。

一、数组存储,再对数组逆序返回

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) { val = x; }
* }
*/
class Solution {
     
    public int[] reversePrint(ListNode head) {
     
        int size=0;
        ListNode h=head;
        while(h!=null){
     
            size++;
            h=h.next;
        }
        int[] ans=new int[size];
        size--;
        while(head!=null){
     
            ans[size--]=head.val;
            head=head.next;
        }
        return ans;
    }
}

这个方法很容易想到,因为返回值要求的就是一个数组,而且逆序对于数组来说只是下标的计算方法稍作改变而已,时间复杂度也很底。

既然能用数组,那么用ArrayList也是可以的,先初始化,然后一路add就可以,最后一样是进行逆向遍历。

二、利用栈

对于逆序这件事的敏感,逆序,是不是就能想到后进先出

那就可以利用栈来实现,也比较简单,全部顺序入栈之后,再出栈就可以。

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) { val = x; }
* }
*/
class Solution {
     
    public int[] reversePrint(ListNode head) {
     
        //用栈保存,类型无需是ListNode,因为我们只需要里面的val
        Stack<Integer> stack = new Stack<Integer>();
        ListNode h=head;
        while(h!=null){
     
            stack.push(h.val);
            h=h.next;
        }
        int size = stack.size();
        //返回值需要数组,这里初始化
        int[] ans = new int[size];
        for (int i = 0; i < size; i++) {
     
            ans[i] = stack.pop();//从栈里取数
        }
        return ans;
    }
}

仔细看一看代码,其实和用数组实现是类似的,而且用栈之后时间还会变的稍微有点慢,毕竟数组的访问更快速。

三、递归

同样的,看到数组的逆序,从第一个元素开始,一直跳到最后一个才开始加入,然后逐渐回来。也容易想到递归

递归的实现,应该是如下步骤:

1.如果到最后一个元素了,开始返回;
2.对剩下的元素递归的调用本方法;
3.把当前值加入结果集;

可以看到,当前值的加入 应该是最后一步做的。

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) { val = x; }
* }
*/
class Solution {
     

    private List<Integer> list=new ArrayList<>();
    
    public int[] reversePrint(ListNode head) {
     
        recur(head);//调用递归方法逆序存储所有val

        //从list里取数就可以
        int size=list.size();
        int[] ans = new int[size];
        for (int i = 0; i < size; i++) {
     
            ans[i] = list.get(i);
        }
        return ans;
    }


    //递归函数
    public void recur(ListNode node){
     
        if(node==null){
     
            return;//如果找到最后一个了开始返回
        }
        recur(node.next);//向下递归
        list.add(node.val);//这样往list里加值的时候保证了逆序
    }
}

四、直接对链表进行翻转

前几种方法都是把链表的数据读出来,之后用某种方式翻转,我们也可以直接对链表进行翻转。

链表的翻转,等同于,从第二个元素开始,依次将箭头方向转过来,指向前一个。

/**
* Definition for singly-linked list.
* public class ListNode {
*     int val;
*     ListNode next;
*     ListNode(int x) { val = x; }
* }
*/
class Solution {
     
    public int[] reversePrint(ListNode head) {
     
        ListNode cur = head;
        ListNode pre = null;
        int size = 0;
        //反转链表,其实是从第二个开始依次把箭头反过去指向前一个
        while (cur != null) {
     
            size++;
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        //反转之后,顺序遍历,存储结果就可以
        int[] ans = new int[size];
        for (int i = 0; i < size; i++) {
     
            ans[i] = pre.val;
            pre = pre.next;
        }
        return ans;
    }
}

这个方法也是比较快的。

其中最核心的部分就是那四行:

            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;

在执行之前我们有,pre(前一个节点)cur(当前节点),以及循环里面创建的next(临时的下一个节点)

1.先保存当前节点cur的下一个节点next;
2.把当前节点的指向pre节点;//翻转了箭头方向
3.接着更新pre节点为cur,也就是后移了;
4.接着更新cur节点为next,也是后移。

后两部就是一个正常的操作后移。遍历到最后就完成了所有箭头的逆向。

你可能感兴趣的:(刷题)