zoukankan      html  css  js  c++  java
  • 《剑指 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版)》,何海涛 著,电子工业出版社

  • 相关阅读:
    IntelliJ IDEA 14.x 快捷键/个性化设置
    Memcache的mutex设计模式 -- 高并发解决方案
    导出/导入Eclipse的workspace配置(备份Eclipse配置)
    URL、URN、URI的区别?
    Thinkpad E440个性化设置:如何/禁用关闭触摸板?
    PHP 正则表达式匹配函数 preg_match 与 preg_match_all
    PHP合并2个数字键数组的值
    编译安装 Zend Opcache 缓存Opcache,加速 PHP
    Linux 新建用户、用户组,给用户分配权限(chown、useradd、groupadd、userdel、usermod、passwd、groupdel)
    alter table锁表,MySQL出现Waiting for table metadata lock的场景浅析及解决方案
  • 原文地址:https://www.cnblogs.com/linfangnan/p/14646265.html
Copyright © 2011-2022 走看看