从尾到头打印链表
来源: https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035
描述
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
如输入{1,2,3}的链表如下图:
返回一个数组为[3,2,1],0 <= 链表长度 <= 1000
示例1
1
2
|
输入:{1,2,3}
返回值:[3,2,1]
|
示例2
1
2
|
输入:{67,0,24,58}
返回值:[58,24,0,67]
|
思路
(1) 一开始很多人拿到这个题,很自然的想到是否直接把链表中的链接结点的指针反转过来,改变链表的方向,那么很自然的就实现了从头到尾的输出了。然而这个方法会改变原来链表的结构, 是否会允许我们在打印链表时候改变链表的结构?这取决于面试官的需求。
(2) 一般是不允许的,打印时不能修改原有链表的结构;那么解决这个链表肯定是需要遍历链表,当前遍历是从头到尾的顺序,要求的顺序是 从尾到头。也就是说 第一个节点最后一个输出, 这不就是 “后进先出"么, 我们立即想到用栈来实现这个顺序。
每经历一个结点的时候,就把它压入到一个栈中,当遍历到整个链表后,再从栈顶开始逐个输出结点的值,此时输出结点的顺序已经反转过来了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 定义链表结构
type ListNode struct {
Val int
Next *ListNode
}
// 打印链表
func (l *ListNode) readLink() {
var result []int
for l != nil {
result = append(result, l.Val)
l = l.Next
}
fmt.Println(result)
}
|
ps: my ugly code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
package main
import . "nc_tools"
import "errors"
/*
* type ListNode struct{
* Val int
* Next *ListNode
* }
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param head ListNode类
* @return int整型一维数组
*/// 实现一个栈
type Stack []interface{}
func (stack *Stack) Push(value interface{}) {
*stack = append(*stack, value)
}
func (stack Stack) Top() (interface{}, error) {
if len(stack) == 0 {
return nil, errors.New("Out of index, len is 0")
}
return stack[len(stack)-1], nil
}
func (stack *Stack) Pop() (interface{}, error) {
theStack := *stack
if len(theStack) == 0 {
return nil, errors.New("out of index, len is 0")
}
value := theStack[len(theStack)-1]
*stack = theStack[:len(theStack)-1]
return value, nil
}
func (stack Stack) IsEmpty() bool {
return len(stack) == 0
}
func printListFromTailToHead( head *ListNode ) []int {
// write code here
var nodeStack Stack
var pNode = new(ListNode)
pNode = head
for pNode != nil {
nodeStack.Push(pNode)
pNode = pNode.Next
}
var res = make([]int, len(nodeStack))
index := 0
for !nodeStack.IsEmpty() {
top, _ := nodeStack.Top()
node := top.(*ListNode)
res[index] = node.Val
_, _ = nodeStack.Pop()
index++
}
return res
}
|
既然都已经用栈来实现了这个函数,我们知道递归本质上就是一个栈结构;很自然的想到了用递归来实现,要想实现反过来输出链表,每访问一个结点的时候,先递归输出它后面的结点,再输出该结点自身, 这样链表的输出结果就反过来了;
1
2
3
4
5
6
7
8
|
func printListFromTailToHead_Recursively(head *ListNode) {
if head != nil{
if head.Next != nil{
printListFromTailToHead_Recursively(head.Next)
}
fmt.Println(head.Val)
}
}
|
上面用递归实现的看起简单, 但是是不是就没什么问题? 答案是否定的;
当链表非常长的时候,就会导致函数的调用层级很深,进而有可能导致函数的调用栈溢出;相比起来,显式的用栈基于循环实现的代码的鲁棒性要更好一些;
测试用例
- 功能测试(输入的链表有多个节点,输入的链表只有一个节点)
- 特殊输入的测试 (输入的链表头结点的指针为nil)
本题考点
- 考察单链表的理解和编程能力;
- 考察对循环,递归和栈3个相互关联的概念和理解;
高手解法与赏析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*
a.实际上是一种往列表头部插入的一个思路了;
b.ArrayList 中有个方法是 add(index,value),可以指定 index 位置插入 value 值
所以我们在遍历 listNode 的同时将每个遇到的值插入到 list 的 0 位置,最后输出 listNode 即可得到逆序链表;
c.复杂度
时间复杂度:O(n)
空间复杂度:O(n)
*/
import java.util.*;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
ListNode tmp = listNode;
while(tmp!=null){
list.add(0,tmp.val);
tmp = tmp.next;
}
return list;
}
}
|