6. 从尾到头打印链表

剑指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个一组等反转时,建议画个图,想清楚链表指向的改变过程之后再写代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值