题目描述
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 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了