JZ6 从尾到头打印链表
链表是存储数据的一种方式,由于内存不是连续的,因此访问只能从表头访问表尾。本题需要从尾部到头打印链表,只能借助特殊的数据结构输出,但是访问顺序不会因此改变。
首先,可以借助递归是到达底层后才会往上回溯的特性来遍历链表。
class Solution { public: void recursion(ListNode* head, vector<int> & res)//注意引用的传参方式 { if(head == nullptr) return;//递归的终止条件 recursion(head->next, res);//访问顺序是从头到尾 res.push_back(head->val);//再填充到数组就是逆序 } vector<int> printListFromTailToHead(ListNode* head) { vector<int> res; recursion(head, res); return res; } };
另外,还有借助栈的后入先出的特性,也能够逆序输出链表。
class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { vector<int> res; stack<int> s; while(head != null){ s.push(head->val); head = head -> next; } while(!s.empty()){ res.push_back(s.top()); s.pop(); } return res; } };
两者的时间复杂度都是O(n),空间复杂度都是O(n),因为递归栈也是占用空间的。需要注意,当链表非常长时,会导致函数调用的层级很深,从而有可能导致函数调用栈溢出。用栈基于循环实现的代码的鲁棒性要好一些。
下面是第三种解法:反转链表
vector<int> printListFromTailToHead(ListNode* head) { ListNode *pre, *tmp, *cur; pre = nullptr; cur = head; while(cur){ tmp = cur -> next; cur->next = pre; pre = cur; cur = tmp; } vector<int> res; while(pre){ res.push_back(pre->val); pre = pre->next; } return res; }
这个要是理解的话最好去牛客网的题解里找一下那个动图,只要理解了pre,cur和tmp三个指针的含义,其实就非常好理解了。
这个优化了空间,为O(1).
最后,向vector插入元素时可以从右向左来遍历,这样也不需要开辟额外的空间,算法复杂度同样是O(1).
class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { vector<int> res; if (head == nullptr) return res; int cnt = 0;//链表数据的长度 ListNode* tmp = head; while(tmp != nullptr){ cnt ++; tmp = tmp->next; } res.resize(cnt);//将res的大小设为链表长度 int k = cnt - 1; while(head != nullptr){ res[k -- ] = head->val;//从右向左存储数据 head = head->next; } return res; } };
参考资料:《剑指offer》,帅地玩编程
O
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?