小知识,大挑战!本文正在参与「程序员必备小知识」创作活动。
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
题目
输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
考察要点
链表的特性, 链表的遍历, 链表的访问
自己的思路
当我看到这个题目时, 首先想到的是用栈来实现从尾到头打印链表. 当我们遍历链表时, 将遍历到的结点放入栈中; 然后访问栈顶元素, 并将该元素出栈.
然后想到的是翻转链表, 也就是每访问到一个结点时, 就将该结点的next指针指向它的前一个结点; 然后再遍历这个翻转后的链表, 就可以做到从尾到头输出链表了.
代码
前期准备
链表结点类
class ListNode {
int value;
ListNode next;
}
复制代码
数据准备
public class LinkedList {
public ListNode head = new ListNode(-1);
public static void main(String[] args) {
LinkedList list = new LinkedList();
ListNode l1 = new ListNode(1);
ListNode l2 = new ListNode(3);
ListNode l3 = new ListNode(5);
ListNode l4 = new ListNode(7);
ListNode l5 = new ListNode(9);
list.head.next = l1;
l1.next = l2;
l2.next = l3;
l3.next = l4;
l4.next = l5;
list.printLinkedListReverselyByStack();
}
}
复制代码
基于翻转链表的实现方式
思路: 每访问一个结点的时候, 就将该结点的next指针指向它的前一个结点, 再遍历翻转后的链表中的每个结点的数据
时空复杂度: O(1)的空间复杂度, O(n)的时间复杂度
优点: 内存占用少
缺点: 更改了原链表的结构, 代码复杂, 不容易理解
public void printLinedListReverselyByOverturn() {
// 拿到头结点
ListNode p = this.head;
// 用来指向首结点
ListNode q = p;
// 用来记录未翻转链表的首结点
ListNode next = p.next;
// 当前头结点的next指针置空
q.next = null;
// 只要下个结点不为空就继续遍历
while (next != null) {
// 用来完成next指针的翻转
p = next;
next = p.next;
p.next = q;
q = p;
}
// 更改头结点的指向
this.head.next = q;
// 遍历翻转后的链表
while (q != null) {
if (q.value == -1) return;
System.out.print(q.value + " ");
q = q.next;
}
System.out.println();
}
复制代码
基于栈的实现方式
思路: 每访问一个结点的时候, 就将该结点中的数据放入栈中, 再遍历栈中的数据
时空复杂度: O(n)的空间复杂度, O(n)的时间复杂度
优点: 思路明确, 代码量少, 结构简单, 易于理解
缺点: 会开辟对应结点数量的内存空间, 有O(n)的空间复杂度
public void printLinkedListReverselyByStack() {
// 拿到头结点
ListNode p = this.head;
// 创建一个栈
Stack<Integer> stack = new Stack<>();
// 遍历链表, 将数据存储到栈中
while (p.next != null) {
p = p.next;
stack.push(p.value);
}
// 遍历栈, 取出栈顶元素, 直到栈空为止
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
System.out.println();
}
复制代码
基于递归的实现方式
思路: 每当要访问一个结点的时候, 我们就先打印它后面结点中的数据, 再打印当前结点中的数据.
时空复杂度: O(n)的空间复杂度, O(n)的时间复杂度
优点: 代码量少, 实现简单, 易于理解, 不更改原链表的结构.
缺点: 当链表中的结点非常多的时候, 就会导致递归层次加深, 占用内存递增, 有O(n)的空间复杂度, 可能会导致内存溢出.
public void printLinkedListByRecurrence() {
// 拿到头结点
ListNode p = this.head;
if (p.next != null) printLinkedListByRecurrence2(p.next);
}
public void printLinkedListByRecurrence2(ListNode node) {
// 当结点为空时, 递归开始返回
if (node != null) {
// 当该结点不为空时, 就打印它的next指向的结点
printLinkedListByRecurrence2(node.next);
// 然后再打印自己结点中的数据
System.out.print(node.value + " ");
}
}
复制代码
总结
本题考察我们对于链表的理解, 如何遍历整个链表; 也考察我们对于栈这种数据结构的使用; 又考察我们对于递归的理解. 一般我们在编程中其实应该尽量避免使用递归, 因为它会开辟大量的栈内存, 进行函数的调用与返回, 当数据量很大的时候, 性能是很低的.