剑指offer 06 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入: head = [1,3,2]
输出: [2,3,1]
限制:
0 < = 链 表 长 度 < = 10000 0 <= 链表长度 <= 10000 0<=链表长度<=10000
小提示:在面试中,如果我们打算修改输入的数据,则最好先问面试官是不是允许修改。
解法一:使用递归,不改变原链表
要逆序打印链表 1->2->3(3,2,1)
,可以先逆序打印链表 2->3(3,2)
,最后再打印第一个节点 1
。而链表 2->3
可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归思想。
Java代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
if(head == null) return new int[]{};
//得到反向遍历链表后的list,然后赋值给数组arr
List<Integer> list = getList(head);
int[] arr = new int[list.size()];
for(int i = 0;i < list.size(); i++){
arr[i] = list.get(i);
}
return arr;
}
//将链表先存到List中
public List<Integer> getList(ListNode head){
List<Integer> list = new ArrayList<>();
if(head.next != null){
list.addAll(getList(head.next));
}
list.add(head.val);
return list;
}
}
go代码
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reversePrint(head *ListNode) []int {
if head == nil {
return []int{} //空切片,写成make([]int,0)亦可
}
res := getResult(head)
return res
}
func getResult(head * ListNode) []int {
var res []int
if head.Next != nil {
res = append(res,getResult(head.Next)...)//go中append添加的是切片类型时,需用...进行拆分添加
}
res = append(res,head.Val)
return res
}
上面基于递归的代码看起来很简洁,但有一个问题,当链表非常长的时候,就会导致函数调用的层级很深,从而可能导致函数调用栈的溢出。故不可取,只是说这个思路在其他题中或许会用到,比如有些不得不用递归的题。
解法二:使用栈,不改变原链表
事实上,看到反向,逆序等词语的时候,我们第一反应就应该是栈的先进后出特性,故在空间复杂度允许的情况下是完全可以考虑先使用栈的。
Java代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
if(head == null) return new int[]{};
Stack<Integer> stack = new Stack<>();
ListNode cur = head;//为了避免使用head = head.next而改变原链表,声明cur来辅助遍历
while(cur != null){
stack.push(cur.val);
cur = cur.next;
}
int[] arr = new int[stack.size()];
//注意这里用的是arr.length;而不是stack.size();因为stack一直在pop,长度在改变
for(int i = 0;i < arr.length; i++){
arr[i] = stack.pop();
}
return arr;
}
}
go代码
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reversePrint(head *ListNode) []int {
if head == nil {
return []int{}//空切片,写成make([]int,0)亦可
}
//go中并没有内置的栈给我们使用,但是提供了list.List,底层是双向链表,可以模拟栈
//Java中的Queue,Deque,Stack底层就都是双向链表
var res []int
stack := list.New()
for head != nil {
stack.PushBack(head.Val)
head = head.Next
}
for stack.Len() > 0 {
element := stack.Back()//拿到最后一个元素的迭代器,迭代器中就一个类型为interface{}的字段Value
stack.Remove(element)//go的List中删除元素需要用到迭代器,这里Back和Remove合在一起使用便模拟了栈的pop操作
res = append(res,element.Value.(int))//注意进行类型断言
}
return res
}
解法三:头插法将链表反转,修改原链表,空间复杂度为 O ( 1 ) O(1) O(1)
头插法:
共三个关键结点,如图:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
if(head == null) return new int[]{};
//由于用的是头插法,即后面的节点需要插在当前头节点之前,所以声明一个虚拟头节点
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy.next.next;//表示本次要插入到dummy后的结点,即插入到原始链表的头节点位置
ListNode tail = head;//tail指向一直不变,改变的是tail.next,用于将已经反转的部分链表和剩余还需要反转的链表相连
int count = 1;//记录节点个数,由于是从第二个节点头插,所以节点个数初始值为1
while(cur != null){
ListNode nxt = cur.next;//记住下一个需要头插的节点,避免等下cur.next改变指向,就找不到原来的cur.next了
cur.next = dummy.next;
dummy.next = cur;
tail.next = nxt;//将已经反转的部分链表和剩余还需要反转的链表相连
cur = nxt;//下一个要头插的节点
count++;
}
int[] arr = new int[count];
ListNode cur2 = dummy.next;
int i = 0;
while(cur2 != null){
arr[i++] = cur2.val;
cur2 = cur2.next;
}
return arr;
}
}
go代码
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reversePrint(head *ListNode) []int {
if head == nil {
return []int{}//空切片,写成make([]int,0)亦可
}
dummy := ListNode{}
dummy.Next = head
cur := dummy.Next.Next//当前需要头插的节点
tail := dummy.Next
for cur != nil {
temp := cur.Next
cur.Next = dummy.Next
dummy.Next = cur
tail.Next = temp
cur = temp
}
var res []int
for cur = dummy.Next;cur != nil;cur = cur.Next {
res = append(res,cur.Val)
}
return res
}
在要修改原链表,如反转,两两反转,k个一组等反转时,建议画个图,想清楚链表指向的改变过程之后再写代码。