《剑指 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版)》,何海涛 著,电子工业出版社