C/C++ BM1反转链表

本文介绍了解决牛客网BM1问题的两种方法:利用栈的特性反转链表和断链重建法。第一种方法通过栈操作实现,第二种方法则是通过改变节点指针顺序实现。

前言

这题是牛客网的BM1,主要涉及到链表的操作以及栈数据结构的使用。

题目

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围: 0 ≤ n ≤ 1000 0≤n≤1000 0n1000
要求:空间复杂度 O ( 1 ) O(1) O(1),时间复杂度 O ( n ) O(n) O(n)

如当输入链表 { 1 , 2 , 3 } \{1,2,3\} {1,2,3}时,
经反转后,原链表变为 { 3 , 2 , 1 } \{3,2,1\} {3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:
Alt

示例1
输入:
{1,2,3}
返回值:
{3,2,1}

示例2
输入:
{}
返回值:
{}
说明:
空链表则输出空

1.解决方案一

1.1 思路阐述

先不考虑链表,我们只考虑数据。对于 1 , 2 , 3 1,2,3 1,2,3的输入,我想让其输出 3 , 2 , 1 3,2,1 3,2,1
这个就类似于栈的先进后出。
那么现在定义栈,存放链表的节点数据,全部存放完之后,再出栈即可。
因为涉及到空链表的情况,所以要单独判断链表是否为空。

C++中使用vector或者stack。vector的适用性更广,包含了stack。
vector中使用back()获取栈顶元素,我这里使用reverse函数,直接倒序,即123变成321,再依次遍历赋值输出。

1.2 源码

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 *  ListNode(int x) : val(x), next(nullptr) {}
 * };
 */
#include <cstdio>
#include <cstdlib>
#include <iterator>
#include <stack>
#include <vector>
class Solution {
  public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param head ListNode类
     * @return ListNode类
     */
    ListNode* ReverseList(ListNode* head) {
        // write code here
        if (head == nullptr)//如果空则直接返回
            return head;
        vector<ListNode*>Contain;//存放链表节点的vector容器
        while (head != nullptr) {
			//容器不为空则依次入栈
            Contain.push_back(head);
            head = head->next;

        }
        //反转容器
        reverse(Contain.begin(), Contain.end());
        ListNode* newHead = Contain[0];
        ListNode* cur = newHead;

        for (int i = 1; i < Contain.size(); i++) {
            cur->next = Contain[i];
            cur = cur->next;
        }
        cur->next = nullptr;//最后一个节点的下一节点指向置空,避免循环链表
        return newHead;
    }
};

2. 解决方案二

2.1 思路阐述

对于给定的顺序链表,将每个节点的后继节点作为当前节点的前置节点,即可完成倒置。
对于1,2,3。我需要将2的下一个节点指向1,新的链表头就是2。
将3的下一个节点指向2,2同时又指向1,那么链表就完成了倒置。

这里我就要用到一个节点用来保存新的表头节点,同时节点与节点之间原来的关系要打乱,要断开原来的先后关系,重新构建。

下面的代码,首先定义了一个新的表头节点pre,置空。将下一个要翻转的表头保存;遍历给定的链表,找到第一个表头p,将这个表头的下一个节点p->next指向新的表头节点p。同时这个表头p原来指向的下一个节点,作为新的倒置表头。新的表头节点pre从空节点指向要翻转表的表头p。

重复上面的步骤,最终得到一个翻转的链表。
这个方法主要是断开原来的链表,组建新的链表的过程。
下面是链表的一个过程。
在这里插入图片描述

2.2 源码

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 *	ListNode(int x) : val(x), next(nullptr) {}
 * };
 */
#include <cstdio>
#include <cstdlib>
#include <iterator>
#include <stack>
#include <vector>
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* ReverseList(ListNode* p) {
         ListNode* pre = nullptr;
     while (p) { // 判断链表是否已经到结尾
         ListNode* t = p->next; // 保留下一个即将翻转的表头
         p->next = pre; // 将现在要翻转的节点的后继指向前一个节点 pre
         pre = p; // 现在的 P 成为下一个前继节点
         p = t; // p 成为下一个要翻转的节点
     }
     return pre;
 
    }
};

总结

本篇博客介绍了两种方式,一种是依赖数据结构本身,使用栈的特性。另一种是断链表,组新链表的方式,语言描述有点搞,具体还是参考代码为好。

### BM1 反转链表问题详解 BM1 是牛客网面试必刷 TOP101 中的一道经典算法题,主要考察链表的操作能力。以下是关于该问题的详细说明和题解。 #### 问题描述 给定一个单向链表的头节点 `pHead`,要求将该链表反转并返回新的头节点。 #### 示例输入输出 - 输入:链表 `1 -> 2 -> 3 -> 4 -> 5` - 输出:链表 `5 -> 4 -> 3 -> 2 -> 1` #### 解题思路 链表反转的核心思想是改变链表中每个节点的指向,使原本的后继节点变为前驱节点。可以通过迭代或递归的方式实现。 #### 方法一:迭代法 迭代法通过逐步遍历链表,调整节点的指向来完成反转操作。具体步骤如下: 1. 初始化三个指针:`pre`(前驱节点)、`cur`(当前节点)和 `temp`(临时存储下一个节点)。 2. 遍历链表,每次将当前节点的 `next` 指向前驱节点,并更新指针位置。 3. 最终返回新的头节点。 代码实现如下: ```cpp class Solution { public: ListNode* ReverseList(ListNode* pHead) { if (pHead == nullptr) return nullptr; // 处理空链表的情况 ListNode* pre = nullptr; // 前驱节点初始化为空 ListNode* cur = pHead; // 当前节点从头节点开始 while (cur != nullptr) { // 遍历链表 ListNode* temp = cur->next; // 保存当前节点的下一个节点 cur->next = pre; // 将当前节点的 next 指向前驱节点 pre = cur; // 更新前驱节点为当前节点 cur = temp; // 更新当前节点为下一个节点 } return pre; // 返回新的头节点 } }; ``` #### 方法二:递归法 递归法通过不断调用自身来反转链表。其核心思想是从最后一个节点开始逐层反转。需要注意的是,递归方法可能会导致栈溢出问题,因此对于大规模链表不推荐使用。 代码实现如下: ```cpp class Solution { public: ListNode* ReverseList(ListNode* pHead) { if (pHead == nullptr || pHead->next == nullptr) { return pHead; // 如果当前节点为空或为尾节点,则直接返回 } ListNode* newHead = ReverseList(pHead->next); // 递归反转后续节点 pHead->next->next = pHead; // 将当前节点的 next 指向自己 pHead->next = nullptr; // 将当前节点的 next 置为空 return newHead; // 返回新的头节点 } }; ``` #### 时间与空间复杂度分析 - **迭代法**: - 时间复杂度:O(n),需要遍历整个链表一次[^1]。 - 空间复杂度:O(1),仅使用了常数级别的额外空间。 - **递归法**: - 时间复杂度:O(n),同样需要遍历整个链表一次[^2]。 - 空间复杂度:O(n),递归调用会占用栈空间,最坏情况下栈深度为 n。 #### 注意事项 1. 链表为空时需特殊处理,避免空指针异常。 2. 在反转过程中,确保不会丢失链表的后续节点。 3. 迭代法通常比递归法更高效且安全。 ### 总结 BM1 反转链表是一道经典的链表操作题目,考察了对链表结构的理解以及基本的算法设计能力。通过上述两种方法可以有效解决该问题,实际应用中推荐使用迭代法以避免递归带来的栈溢出风险。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

澄澈i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值