《剑指 Offer》学习记录:题 6:从尾到头打印链表

面试题 6:从尾到头打印链表

题干

题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。——《剑指 Offer》P58

这道题可以直接将链表的元素转存到另一个数组或 vector 中反向输出,也可以使用 vector 的 reverse() 方法。这些方法都较为简单粗暴,博客中就不对这些方法进行讨论。

测试样例

链表的数据结构定义如下(C++):

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

若传入的头结点 head 指向的链表结构如图所示:

则输出(或返回)的序列为:

[5,4,3,2,1]

蛮力法

方法思路

要获取链表的第 i 个元素,就必须先遍历到第 i - 1 个元素。因此可以先统计出链表的长度 n,然后遍历 n 次链表,依次将所有数据元素取出。

题解代码

需要注意的是,使用蛮力法需要额外对空表进行判断,否则将会触发运行时错误。

class Solution {
public:
    vector<int> vec;
    vector<int> reversePrint(ListNode* head) {
        ListNode* ptr = head;
        int count = 0;   //链表结点总数

        if(head == NULL)    //空表判断
        {
            return vec;
        }
        while(ptr->next != NULL)    //统计表长
        {
            count++;
            ptr = ptr->next;
        }
        vec.push_back(ptr->val);    //获得表尾元素
        /*依次遍历链表,直到取出所有元素*/
        for(int i = count - 1; i >= 0; i--)
        {
            ptr = head;
            for(int j = 0; j < i; j++)
            {
                ptr = ptr->next;
            }
            vec.push_back(ptr->val);
        }
        return vec;
    }
};

时空复杂度

设链表的长度为 n,蛮力法需要遍历 n 次链表,每次遍历的数据元素的总数为 (n + 1) / 2。因此 T(n) = (n^2 + n) / 2,进而得到 O(n) = n ^ 2。
由于不需要辅助空间,而是直接遍历链表,所以空间复杂度为 O(1)。

递归法

方法思路

由于链表是种不支持随机访问的结构,所以想要获取链表的第 i 个元素,就必须先遍历到第 i - 1 个元素。所以这道题的关键点在于如何在遍历完链表之后,得到之前已经访问过的属性。如果是重新遍历,则需要较大的时间复杂度开销,这样效率就低了。
其实和二叉树的 3 种序列遍历的思想相同,其实无论是前序、中序还是后序遍历,是输出的语句的位置不同而实现的。也就是说可以使用递归去遍历链表,在递归达到出口进行回溯的时候,再把每个元素取出。也就是先执行递归函数 “fun(node->next)”,再执行 “cout << node->val”,在递归回溯的时候就可以实现逆序访问的效果。

题解代码

class Solution {
public:
    vector<int> vec;    //存储逆序的链表中的数据
    vector<int> reversePrint(ListNode* head) {
        visitNextNode(head);
        return vec;
    }

    void visitNextNode(ListNode* node) {
        if(node == NULL){
            return;    //遍历到表尾,开始回溯
        }
        fun(node->next);
        vec.push_back(node->val); 
    }
};

时空复杂度

设链表的长度为 n,该方法需要遍历链表的每个结点,也就是需要递归 n 次,所以时间复杂度为 O(n)。
由于递归需要额外的内存存储信息,递归函数的深度为 n,所以空间复杂度也是 O(n)。

栈辅助法

方法思路

想要获取链表的第 i 个元素,就必须先遍历到第 i - 1 个元素,因此在减小空间复杂度的情况下想要逆序输出,可以考虑回溯遍历的状态。除了使用递归,也可以自然地想到栈结构。因为栈结构具有“先进后出”的特点,如果将链表的元素按顺序入栈,最后出栈得到的次序就是逆序的序列了。例如现有如下链表结构:

将链表的所有元素入栈,就能得到如下的栈结构,此时将元素依次出栈即可实现逆序。

题解代码

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> vec;
        stack<int> a_stack;

        /*将链表的所有元素入栈*/
        while(head != NULL){
            a_stack.push(head->val);
            head = head->next;
        }

        /*将所有元素出栈*/
        while(!a_stack.empty()){
            vec.push_back(a_stack.top());
            a_stack.pop();
        }
        return vec;
    }
};

时空复杂度

设链表的长度为 n,该方法需要遍历链表的每个结点,所以时间复杂度为 O(n)。
由于需要栈作为辅助结构,栈的大小为 n,所以空间复杂度也是 O(n)。由于只需要栈结构来存储,不需要占用递归的开销,因此实际占用的空间会比递归法要小。

参考资料

《剑指 Offer(第2版)》,何海涛 著,电子工业出版社

posted @ 2021-04-15 22:48  乌漆WhiteMoon  阅读(91)  评论(0编辑  收藏  举报