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

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

题干#

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

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

测试样例#

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

Copy Highlighter-hljs
struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} };

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

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

Copy Highlighter-hljs
[5,4,3,2,1]

蛮力法#

方法思路#

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

题解代码#

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

Copy Highlighter-hljs
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”,在递归回溯的时候就可以实现逆序访问的效果。

题解代码#

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

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

题解代码#

Copy Highlighter-hljs
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 @   乌漆WhiteMoon  阅读(112)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS