力扣×剑O 06.从尾到头打印链表
剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
第一反应的思路很简单,遍历并保存,然后逆序保存并返回另一个数组。很明显虽然时间、空间代价O(n)。
1 |
|
稍微想一下还可以有个略微好点的方案:用前后两个指针,遍历的时候把前一个结点和当前节点逆转,最后遍历完之后再反过来遍历一遍,保存到一个数组。仍然时间、空间代价O(n)),但好处是节省了一个大小为n的数组的空间开销。
书上再一次强调,要分情况,因为原来的链表不一定允许被修改。
看到这道题后 , 很多人的第一 反应是从头到尾输出将会 比 较简单,于是我们很自然地想到把链表中链接节点的指针反转过来 ,改变链表的方向,然后就可以从头到 尾输出了 。 但该方法会改变原来链表的结构 。是否允许在打印链表的时候修改链表的结构?这取决千面试官的要求,因此在面试的时候我们要询问清楚面试官的要求 。
书上的第一个例子和我的代码思路相同,同样是用了先进后出的性质,但我只是用数组模拟了这种特点,书上用了真正的栈。
1 | void Print ListReversingly_lteratively(ListNode* pHead) |
第二个解法我是想不出的,不理解“递归在本质上就是 一个栈结构”这句话。
既然想到了用栈来实现这个函数,而递归在本质上就是 一个栈结构 ,于是很自然地又想到了用递归来实现 。 要实现反过来输出链表 ,我们每访问到 一个节点的时候 , 先递归输出它后面 的节点 , 再输 出该节点自身 ,这样链表的输出结果就反过来了 。
网上查了一下,大概是因为:
当递归调用时每次调用自己时可以看做是压栈过程,当递归条件满足结束时,递归一级一级的返回时可以看做是出栈的过程。
我自己的理解是:每次函数自己调用自己的时候相当于一级一级按顺序把旧的状态、新的状态依次放入栈中,等到到达了底层开始返回,那么就开始从最上层最新的状态开始逐级恢复,也就是出栈。
后面的代码很容易理解,写起来也很容易
1 | /* |
第二种方法存在的问题是:
当链表非常长的时候 , 就会导致函数调用的层级很深 ,从而有可能导致函数调用栈溢出 。显然用栈基千循环实现的代码的鲁棒性要好 一 些 。
评论区给出了另一种方法:不用栈,不用递归。核心思想在于遍历两次链表,第一次遍历只记录个数,第二次遍历时逆序保存数据到同样大小数组中,遍历完成后最后返回数组。
1 | std::vector<int> Solution::reversePrint(const ListNode* head) |