leetcode206/剑指 Offer 24. 反转链表

题目描述

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法1:迭代/双指针

假设存在链表 1→2→3→∅1 ,我们想要把它改成 ∅←1←2←3
在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。不要忘记在最后返回新的头引用

我自己的提交

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
   ListNode  end=null ;
   ListNode  cur=null;
   ListNode  temp=null;
   while(head!=null)
   {
    
    
       temp=head;//temp指向当前节点
       cur=head.next;//先暂存当前节点的下一个节点,因为要改变当前节点的指向,不暂存的话,就访问不到下一个节点了
       temp.next=end;//把当前节点指向前一个节点
       end=temp; //改变新链表的头结点    
       head=cur;//改变当前节点,如果原来是1,2,3,4,head是1,依次把head指向2,3,4
   }
    return temp;
    }
}

在这里插入图片描述
也可以把代码简化一下

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
   ListNode  end=null ;
   ListNode  cur=head;
   ListNode  temp=null;
   while(cur!=null)
   {
    
       
       temp=cur.next;//先暂存当前节点的下一个节点,因为要改变当前节点的指向,不暂存的话,就访问不到下一个节点了
       cur.next=end;//把当前节点指向前一个节点
       end=cur; //改变新链表的头结点      
       cur= temp;
   }
    return end;
    }
}

在这里插入图片描述
看一遍就理解,图解单链表反转

在这里插入图片描述

时间复杂度:O(N)。其中N为链表长度,需要从前至后遍历一次链表。
空间复杂度:O(1)。

方法2:递归

视频讲解:迭代 和 递归【很容易理解】
为什么可以使用递归
把反转链表拆分成反转头结点和反转除头结点的链表,子链表又可以拆成一个头结点和更短的子链表
在这里插入图片描述

递归终止条件:只有一个元素就不需要进行反转,递的过程中只剩一个元素的时候开始反转(归)
在这里插入图片描述
递的过程
在这里插入图片描述
当链表节点只有一个就不需要进行反转了

然后进行归的操作
head.next,next=head;
在这里插入图片描述
head.next=null;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
递归过程分析,不要以为整个程序执行的过程中head没有改变, reverseList(ListNode head)这个函数,我们传进去的就是head,而我们递归调用的时候, reverseList(head.next);,用的是head.next,每一次递归都会执行next操作

reberseList(head)
reberseList(head.next)
reberseList((head.next).next)
reberseList((head.next).next).next)
直到递归终止条件,开始返回

在这里插入图片描述
代码展示

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
         if(head==null||head.next==null)//链表本身只有一个节点就不用反转,或者递到最后一个节点开始归
         {
    
    
             return head;
         }   
         ListNode node=reverseList(head.next);
         head.next.next=head;
         head.next=null;
         return node;        
    }
}

在这里插入图片描述

时间复杂度:O(N)。其中N为链表长度,递归树深度为N。
空间复杂度:O(N)。递归所需栈空间为N。

方法3:容器法

先存放进一个ArrayList,然后反转 (Collections.reverse ) ,再定义next关系

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
        List<ListNode>list=new ArrayList<>();
        while(head!=null)//把链表节点存进容器
        {
    
    
           list.add(head);
           head=head.next;
        }
        Collections.reverse(list);//反转容器中的顺序
        int size=list.size();
        if(size==0)
        {
    
    
            return null;
        }
        for(int i=0;i<size-1;i++)//定义next关系
        {
    
    
           
            list.get(i).next=list.get(i+1);
        }
        list.get(size-1).next=null;//给最后一个节点的next置空,如果不加上这句会报错形成环
        return list.get(0);//返回链表头结点
    }
}

在这里插入图片描述
注意 list.get(size-1).next=null;//给最后一个节点的next置空,如果不加上这句会报错形成环

在这里插入图片描述

这是容器法的另一种实现
在这里插入图片描述

方法4: 栈

class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
         if(head==null||head.next==null)//链表本身只有一个节点就不用反转,或者递到最后一个节点开始归
         {
    
    
             return head;
         }   
        Stack<ListNode>stack=new Stack();
        while(head!=null)//入栈
        {
    
    
            stack.push(head);
            head=head.next;
        }
       
         ListNode temp=stack.pop(); 
         ListNode node=temp;
        while(!stack.empty())//从栈中弹出,重新形成链表
        {
    
    
           temp.next=stack.pop(); 
           temp= temp.next;
        }  
         temp.next=null;  //不加会形成环
         return node;        
    }
}

在这里插入图片描述

注意: temp.next=null; //不加会形成环
在这里插入图片描述
我分析了一下这个环

在这里插入图片描述

时间复杂度:O(N+N) = O(N)。其中N为链表长度,需要从前至后遍历一次链表将节点入栈、再将栈中所有节点全部弹出。
空间复杂度:O(N)。使用了辅助栈,空间大小为N。

总结:

1:获取ArrayList的元素,list.get(index)

2:扯下node.next的遮羞布

 ListNode node=new ListNode(6);
 ListNode node_1=node; 
 System.out.println(node_1.next.val);

node_1.next为null,调用val属性会爆出空指针异常
在这里插入图片描述

ListNode node=new ListNode(6);
ListNode node_1=node;
System.out.println(node_1.next);

我们空指针不调用val属性就不会报错了
在这里插入图片描述

ListNode node=new ListNode(6);
ListNode node_1=node;
node.next=new ListNode(7);
System.out.println(node_1.next.val);

明明是给node指定了next,但是 node_1的next也能访问到7,这也能解释为什么通常遍历链表,用一个临时节点指向头结点就能遍历完整条链表
在这里插入图片描述

从下图,我们可以看出,node和node_1指向的是同一个节点,那么我们可不可以理解为,节点只有一个(因为只new了一次),只不过,这个节点现在有两个名字,你可以叫它node,也可以叫它node_1.它们会以同样的方式回答你。
在这里插入图片描述

现在有一条链表1->2->3->4->5->6,我们来看看遍历的过程

        while(node1!=null)
        {
    
    
            System.out.println(node1.val);
            node1=node1.next;
        }

现在节点1的名字叫node1,
在这里插入图片描述
经过一次次遍历,现在node1是节点6的名字了,那我们现在根据node1这个名字就只能找到节点6,而不能找到原来的节点1了
在这里插入图片描述

参考

滚滚在leetcode刷题-206-反转链表

【四种方法】剑指 Offer 24. 反转链表【递归:先序/后序】、【非递归:使用栈/不使用栈】

如何递归反转链表

猜你喜欢

转载自blog.csdn.net/ningmengshuxiawo/article/details/115036103