题目描述
输入一个链表,从尾到头打印链表每个节点的值。
输入描述
链表表头
输出描述
需要打印的链表的表头
解题思路
这种题目一般情况下会使用反转链表的方法解决。
对于单链表来说,其逆序方法和普通的切片/数组逆序方法是有一定区别的,在逆序过程中有一个很重要的点:在修改节点指针域的时候,一定要记录下后继节点的地址,否则将丢失后继节点。
关于反转链表,一般有三种解决方法。
就地逆序
主要思路是:在遍历链表时,修改当前节点指针域的指向,让其指向它的前驱节点。这样,如果需要修改一个节点的后继指针,应涉及到三个点:前驱,当前,后继三个节点。
// 我们考虑链表带有头结点的情况
func Reverse(node *LNode) {
if node == nil || node.Next == nil {
return
}
// 对应三个点:前驱,当前,后继
var pre *LNode
var cur *LNode
next := node.Next
for next != nil {
cur = next.Next
next.Next = pre
pre = next
next = cur
}
node.Next = pre
}
递归法
主要思路是:每一次选择其中一条子链进行逆序,然后再把剩下的一个点街道逆序的子链后面,直到所有元素全部执行完逆序。
func RecursiveReverseChild(node *LNode) *LNode {
if node == nil || node.Next == nil {
return node
}
newHead := RecursiveReverseChild(node.Next)
node.Next.Next = node
node.Next = nil
return newHead
}
func RecursiveReverse(node *LNode) {
firstNode := node.Next
newHead := RecursiveReverseChild(firstNode)
node.Next = newHead
}
之中递归的方法算是一种比较直观也比较容易理解的方法,但确实有的时候不太容易想的到…
插入法
插入法使用的是头插法,即从链表的第二个节点开始,把遍历到的节点插入到头结点的后面,直到遍历结束。
func InsertReverse(node *LNode) {
if node == nil || node.Next == nil {
return
}
var cur *LNode
var next *LNode
cur = node.Next.Next
// 设置链表第一个节点为尾节点
node.Next.Next = nil
for cur != nil {
next = cur.Next
cur.Next = node.Next
node.Next = cur
cur = next
}
}
这种方法应该是相比前两种更加有优势的一种方法。
但这三种方法只完成了逆序的操作,题目还要求需要对其进行输出。
同样对应着三种方法:
就地逆序+顺序输出
头插法+顺序输出
递归输出
着重说一下最后这种方法,这种方法很简单,并不需要对原先的链表进行调整,直接输出即可:
func ReversePrint(node *LNode) {
if node == nil {
return
}
ReversePrint(node.Next)
fmt.Println(node.Data, " ")
}
这里要注意,在剑指Offer中为我们在面试中提出了如下小提示:
在面试时候,如果我们打算修改输入的数据,最好先问问面试官是不是允许修改
如果要求修改,那么头插法最合适不过了,但如果不让修改的话,直接递归输出即可。