1 题目
leetcode
输入一个链表的头结点,从尾到头反过来打印每个节点的值。(用数组返回)
2 解
2.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[] {};
//只有一个元素的链表
if(head.next==null) {
return new int[] {head.val};
}
//第一次循环,统计长度
ListNode fListCrycleNode=head;
int count=1;
while(fListCrycleNode.next!=null) {
count++;
fListCrycleNode=fListCrycleNode.next;
}
int [] res=new int[count];
//反向填充
while(head.next!=null) {
res[--count]=head.val;
head=head.next;
}
res[--count]=head.val;
head=head.next;
return res;
}
}
2.2 优化解
在官解下方找到了同样思路一个解法,代码如下
public int[] reversePrint(ListNode head) {
//先获取链表长度,创建对应长度数组
ListNode currNode = head;
int len = 0;
while(currNode != null){
len ++;
currNode = currNode.next;
}
int[] result = new int[len];
//再次遍历链表,将值倒序填充至结果数组
currNode = head;
while(currNode != null){
result[len - 1] = currNode.val;
len --;
currNode = currNode.next;
}
return result;
}
可以看出同样的解法他的代码要简洁很多,区别在于对空情况的判断和对循环条件的设置。
这两个问题都是源自于我对空指针的理解不够,空指针的原因是对调用了空对象的方法或访问其属性,仅仅是赋值是不会触发空指针的。
当然他的代码也可以进一步优化,比如
1.将result[len-1] = currNode.val; len--;
合并为result[--len]=currNode.val
2.第二次循环直接使用head自身来循环,
//再次遍历链表,将值倒序填充至结果数组
while(head != null){
result[--len] = head.val;
head = head.next;
}
3 官解
主要有栈和递归两种解法
3.1 辅助栈
首先遍历一次链表,将链表中的值压入栈中,然后创建数组,数组的长度可以由栈的大小得出,将栈中元素取出并赋值给数组元素。这个方法利用了栈先进后出的特点。
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack=new Stack<ListNode>();
//首次遍历 入栈
while (head!=null) {
stack.push(head);
head=head.next;
}
//创建数组
int []res=new int[stack.size()];
for (int i = 0; i < res.length; i++) {
res[i]=stack.pop().val;
}
return res;
}
3.2 递归
递归在本质上也是一种栈结构,访问一个节点时,先输出它后面的节点,再输出节点自身。
使用递归的难点我觉得在于如何存储递归访问到的结果,一开始真的没有头绪,后来发现他们都是使用ArrayList存储的,每遇到一个新的,就将其add进去,最后遍历该ArrayList取出。
ArrayList<Integer> list=new ArrayList<>();
public int[] reversePrint(ListNode head) {
//递归
recursive(head);
//创建数组
int[] res=new int[list.size()];
//循环赋值
for(int i=0;i<res.length;i++){
res[i]=list.get(i);
}
return res;
}
public void recursive(ListNode head){
if(head==null){
//中止条件
return;
}else{
//先访问下一个节点
recursive(head.next);
//保存当前节点的值
list.add(head.val);
//返回
return;
}
}
3.3 头插法
头插法即新建一个链表,包含一个头结点,然后遍历原链表时将原链表的节点作为头结点后第一个节点插入链表中,得到的新链表就是逆序的了,然后遍历新链表即可。
public int[] reversePrint(ListNode head) {
//新建链表
ListNode newList=new ListNode(-1);
int count=0;
while (head!=null) {
//原链表的下一个节点
ListNode temp=head.next;
//将head放到新链表上
head.next=newList.next;
newList.next=head;
//移动到原链表的下一个节点
head=temp;
//统计长度
count++;
}
//新建数组
int []res=new int[count];
//跳过新链表的头结点
newList=newList.next;
//开始遍历
for (int i = 0; i < res.length; i++) {
res[i]=newList.val;
newList=newList.next;
}
return res;
}