LeetCode 206. 反转链表(C++实现)

一、题目描述

反转一个单链表。

示例:

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


进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

 

二、迭代法

核心思想:

设立三个指针,依次完成反转,在反转之前需要存储下一个结点的地址,否则后面的结点会丢失。

  • head 指针:待反转的结点。
  • next 指针 :后继节点即 next = head ->next。
  • pre 指针:前驱结点,初始化为 NULL,因为头结点的 next 指针最终指向 NULL。

 

首先看一下原链表:     

          

 

总共需要添加两个指针,prenext 。初始化 pre 指向 NULL。

                     

然后就是迭代的步骤,总共四步,顺序一步都不能错。

  •  next 指向 head 的 next,防止原链表丢失

  • head next 从原来链表脱离,指向 pre

  • pre 指向 head

  • head 指向 next

 

一次迭代就完成了,如果再进行一次迭代就变成下边的样子。

可以看到整个过程无非是把旧链表的 head 取下来,添加的新链表头部。接下来就是停止条件了,我们再进行一次循环:

                                

可以发现当 head 或者 next 指向 null 的时候,我们就可以停止了。此时将 pre 返回,便是逆序了的链表了。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //空链表和一个结点的链表都无需反转
        if(head == NULL || head->next == NULL) return head;
        ListNode *pre = NULL,*cur = head, *next = head;
        while(cur != NULL){
            next = cur->next;   //保存cur的next,以防止cur改变后丢失
            cur->next = pre;    //逐个结点反转
            pre = cur;          //更新指针位置
            cur = next;
        }
        return pre;     //返回反转后的头结点
    }
};

关于链表的题,记住更改指向的时候要保存之前的节点,不然会丢失节点。

可参考这篇博客和这篇。其实这种方式就是原地链表的翻转,即没有使用额外的空间。

这种模式的适用场景:

  • 翻转链表中的一段(中等)
  • 翻转每k个元素为一组的子链表段(中等)

 

三、递归法

  • 首先假设我们实现了将单链表逆序的函数,ListNode* reverseList(ListNode* head) ,传入链表头,返回逆序后的链表头。
  • 接着我们确定如何把问题一步一步的化小,我们可以这样想。

     把 head 结点拿出来,剩下的部分我们调用函数 reverseList,这样剩下的部分就逆序了,接着我们把 head 结点放到新链表       的尾部就可以了,这就是整个递归的思想了。

  •  head 结点拿出来,对子链表进行递归:即 ListNode* newList = reverseList(head->next);递归结果如下:

 

  • 此时第一个结点和第二个结点间还没有链接好(也没有断开,只是还未反转),让2结点的 next 指向1,1的 next 指向 null 就ok了。

  • 找到递归出口

     当然就是如果结点的个数是一个,那么逆序的话还是它本身,直接 return 就够了。怎么判断结点个数是不是一个呢?它               的 next 等于 null 就说明是一个了。但如果传进来的本身就是 null,那么直接找它的 next 会报错,所以先判断传进来的是     不是 null ,如果是,也是直接返回就可以了。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //第一个条件是判断递归开始,传入的参数的合法性。第二个是递归的终止条件
        if(head == NULL || head->next == NULL)
            return head;
        ListNode* newhead = reverseList(head->next);  //开始进行递归,先反转后面的链表,走到链表的末端
        head->next->next = head;                      //第二个结点的 next 指向第一个结点
        head->next = NULL;                            //第一个结点的 next 指向NULL
        return newhead;
    }
};

 

总结:

对于递归算法,最重要的就是明确递归函数的定义。具体来说,我们的 reverseList 函数定义是这样的:

    输入一个节点 head,将「以 head 为起点」的链表反转,并返回反转之后的头结点

明白了函数的定义,在来看这个问题。比如说我们想反转这个链表:

那么输入 reverseList(head)后 ,会在这里进行递归:

 ListNode* newhead = reverseList(head->next); 

不要跳进递归(你的脑袋能压几个栈呀?),而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果:

这个  reverseList(head)执行完成后,整个链表就成了这样:

并且根据函数定义,reverseList 函数会返回反转之后的头结点,我们用变量  newhead(在图中是 last)接收了。

现在再来看下面的代码:

head->next->next = head; 
head->next = NULL;                           
return newhead;

神不神奇,这样整个链表就反转过来了!递归代码就是这么简洁优雅,不过其中有两个地方需要注意:

1、递归函数要有 递归基,也就是这句:

 if(head == NULL || head->next == NULL)
     return head;

意思是如果链表只有一个节点的时候反转也是它自己,直接返回即可。

2、当链表递归反转之后,新的头结点是 last,而之前的 head 变成了最后一个节点,别忘了链表的末尾要指向 null:

head->next = NULL;  

来自文章1,参考链接1

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潇湘夜雨~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值