【算法与数据结构】【链表篇】【题1-题5】

news2024/11/8 13:43:19

题1.从尾到头打印链表

题目:输入一个链表的头结点,从尾到头反过来打印出每个节点的值。链表的定义如下:

struct ListNode
{
    int mValue;
    ListNode *mNext;
    ListNode *mPrev;
};

1.1 方法一:栈

思路:要反过来打印,首先需要遍历,那么从先遍历的节点后打印,典型的后进先出,符合栈的结构。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

struct ListNode
{
    int mValue;
    ListNode *mNext;
};

void reverselist(ListNode *pHead)
{
    if (pHead == nullptr)
    {
        return;
    }

    stack<ListNode *> reversenodes; // 创建一个栈容器

    ListNode *node = pHead;

    while (node != nullptr) // 遍历链表
    {
        reversenodes.push(node); // 将其放入栈容器中
        node = node->mNext;
    }

    while (!reversenodes.empty()) // 循环从栈取出节点
    {
        node = reversenodes.top();
        cout << node->mValue << endl;
        reversenodes.pop();
    }
}

// 添加到链表的尾部
struct ListNode *AddToTail(ListNode *pHead, int value)
{
    ListNode *pNew = new ListNode(); // 创建了一个子节点
    pNew->mValue = value;
    pNew->mNext = nullptr; // 因为是向尾部节点插入值,所以其下一个节点一定是空

    if (pHead == nullptr) // 如果头结点为空,则当前节点为头结点
    {
        pHead = pNew;
    }
    else // 如果头结点不为空
    {
        ListNode *pnode = pHead; // 则先取出链表的头结点

        while (pnode->mNext != nullptr) // 如果当前节点有子节点,则说明不是最后一个节点
        {
            pnode = pnode->mNext;
        }

        pnode->mNext = pNew; // 找到了最后的节点,将此时要添加的节点放到链表的尾部
    }

    return pHead;
}

int main()
{

    ListNode *Head = nullptr;
    Head = AddToTail(Head, 1);
    Head = AddToTail(Head, 2);
    Head = AddToTail(Head, 3);
    reverselist(Head);
    return 0;
}

1.2 方法二:递归

思想:递归从本质上将就是一个栈的结构,后进先出,所以我们可以当输出一个节点时候,先输入其下一个节点,然后再输出当前节点。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

struct ListNode
{
    int mValue;
    ListNode *mNext;
};


void recursionlist(ListNode *pHead)
{
    if(pHead == nullptr)//注意递归的返回条件
    {
        return;
    }

    if(pHead->mNext != nullptr)//如果当前节点还有子节点,则递归先打印子节点的值
    {
        recursionlist(pHead->mNext);
    }

        cout<<pHead->mValue<<endl;
}

    


// 添加到链表的尾部
struct ListNode *AddToTail(ListNode *pHead, int value)
{
    ListNode *pNew = new ListNode(); // 创建了一个子节点
    pNew->mValue = value;
    pNew->mNext = nullptr; // 因为是向尾部节点插入值,所以其下一个节点一定是空

    if (pHead == nullptr) // 如果头结点为空,则当前节点为头结点
    {
        pHead = pNew;
    }
    else // 如果头结点不为空
    {
        ListNode *pnode = pHead; // 则先取出链表的头结点

        while (pnode->mNext != nullptr) // 如果当前节点有子节点,则说明不是最后一个节点
        {
            pnode = pnode->mNext;
        }

        pnode->mNext = pNew; // 找到了最后的节点,将此时要添加的节点放到链表的尾部
    }

    return pHead;
}

int main()
{

    ListNode *Head = nullptr;
    Head = AddToTail(Head, 2);
    Head = AddToTail(Head, 3);
    Head = AddToTail(Head, 4);
    recursionlist(Head);
    return 0;
}

题2:两数相加

题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

 难度:中等

思路:

我们知道其数字为逆序,那么两个链表的相同位置可以直接相加,如果相同位置相加值大于10,则代表要在下一位相加的时候,加1进位。

需要注意的点是:当两个链表都遍历完成,但是进位标志仍然为1,代表我们需要再创建一个节点用于进位。例如:链表1为999,链表二为1,两个相加,当访问完链表1的第三个位置时,循环退出,此时我们的结果为000并且进位标志不为0,因此我们要再创建一个节点存储1的结果,然后将其输出变为0001

代码一

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution
{
public:
    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2)
    {
        if (l1 == nullptr) // 如果第一个链表为空,则返回第二个链表
        {
            return l2;
        }

        if (l2 == nullptr) // 如果第二个链表为空,则返回第一个链表
        {
            return l1;
        }

        ListNode *returnlistPhead = nullptr; // 先创建返回链表的头节点链表
        ListNode *returnlistptail = nullptr; // 先创建返回链表的头节点链表

        int carry = 0; // 代表进位


        while (l1 != nullptr || l2 != nullptr)
        {
            ListNode *node = new ListNode();
            if (l1 != nullptr && l2 != nullptr)
            {
                if (carry + l1->val + l2->val < 10) // 如果两位相加不超过10,则不需要进位
                {
                    node->val = carry + l1->val + l2->val;
                    carry = 0; // 重置进位,代表当前位数不需要进位
                }
                else
                {
                    node->val = carry + l1->val + l2->val - 10; // 如果两位相加超过10,则需要进位
                    carry = 1;                                  // 代表当前位数需要进位
                }

                l1 = l1->next;
                l2 = l2->next;
            }
            else if (l1 == nullptr) // 此时l2不为空
            {
                if (carry + l2->val < 10) // 如果两位相加不超过10,则不需要进位
                {
                    node->val = carry + l2->val;
                    carry = 0; // 重置进位,代表当前位数不需要进位
                }
                else
                {
                    node->val = carry + l2->val - 10; // 如果两位相加超过10,则需要进位
                    carry = 1;                        // 代表当前位数需要进位
                }

                l2 = l2->next;
            }
            else if (l2 == nullptr) // 此时l1不为空
            {
                if (carry + l1->val < 10) // 如果两位相加不超过10,则不需要进位
                {
                    node->val = carry + l1->val;
                    carry = 0; // 重置进位,代表当前位数不需要进位
                }
                else
                {
                    node->val = carry + l1->val - 10; // 如果两位相加超过10,则需要进位
                    carry = 1;                        // 代表当前位数需要进位
                }

                l1 = l1->next;
            }

            // 插入链表中
            if (returnlistPhead == nullptr) // 如果返回的链表第一个元素为空
            {
                returnlistPhead = returnlistptail = node;
                returnlistptail->next = nullptr;
            }
            else
            {
                returnlistptail->next = node;
                returnlistptail = returnlistptail->next;
            }
        }

        if(carry == 1)//当我们每次使用进位后都会将carry重置为0,而此时代表链表一和链表二都遍历完毕,但是还有一次进位没使用,所以要多加一位
        //例如链表1为999,链表2为 1,此时必须多创建一个节点,用以进位
        {
            ListNode *node = new ListNode(1, nullptr);
            returnlistptail->next = node;   
        }

        return returnlistPhead;
    }
};

代码二:

此处主要是将上面的代码简化。

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution
{
public:
    ListNode *addTwoNumbers(ListNode *l1, ListNode *l2)
    {
        if (l1 == nullptr) // 如果第一个链表为空,则返回第二个链表
        {
            return l2;
        }

        if (l2 == nullptr) // 如果第二个链表为空,则返回第一个链表
        {
            return l1;
        }

        ListNode *prevreturnPhead = new ListNode(-1,nullptr); // 先创建返回链表的头节点链表的前一个节点
        ListNode *prevreturntail = prevreturnPhead;

        int carry = 0; // 代表进位

        while (l1 || l2 || carry)
        {
            int sum = 0;
            if (l1)
            {
                sum += l1->val;
                l1 = l1->next;
            }

            if (l2)
            {
                sum += l2->val;
                l2 = l2->next;
            }

            sum += carry;
            prevreturntail->next = new ListNode((sum % 10),nullptr);
            prevreturntail = prevreturntail->next;
            carry = sum /10;
        }

        return prevreturnPhead->next;
    }
};

题3:删除链表的倒数第n个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

难度:中等

 3.1方法一:递归

使用cur标志位找到倒数要删除的节点,如果是要删除的节点,则将当前节点的next指针指向下一个节点的next指针,如果不是要删除的节点,则指针保持不变。

注意在递归调用中指针名称相同,但其代表的含义是不同的。

class Solution
{
public:
    int cur = 0;
    ListNode *removeNthFromEnd(ListNode *head, int n)
    {
        if (!head)
            return NULL;
        head->next = removeNthFromEnd(head->next, n);
        cur++;
        if (n == cur)
            return head->next;
        return head;
    }
};

3.2 方法二:栈

在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。

第一步:添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。

第二步:将其循环放入栈中。

第三步:找到要删除的元素的上一个元素,即栈顶元素。

第四步:重置要删除的元素的上一个元素的next指针为删除元素的next指针。

第五步:返回哑节点的next指针,即为链表的头结点。

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution
{
public:
    int cur = 0;
    ListNode *removeNthFromEnd(ListNode *head, int n)
    {
        if (head == nullptr || n <= 0)
        {
            return nullptr;
        }

        ListNode *dummynode = new ListNode(0, head);//添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了
        ListNode *dummynode2 = dummynode;
        
        stack<ListNode *> stacklists;

        while (dummynode2 != nullptr)
        {
            stacklists.push(dummynode2);
            dummynode2 = dummynode2->next;
        }

        while (n != 0 && !stacklists.empty())
        {
            n--;
            stacklists.pop();
        }

        ListNode *prevnode = stacklists.top();//要删除节点的上一个节点

        prevnode->next = prevnode->next->next;

        ListNode *returnhead = dummynode->next;

        return returnhead;
    }
};

题4:两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

思路:

第一步创建哑结点 dummy,令 dummy.next = head。令 temp 表示当前到达的节点,初始时 temp = dummy。每次需要交换 temp 后面的两个节点。

如果 temp 的后面没有节点或者只有一个节点,则没有更多的节点需要交换,因此结束交换。否则,获得 temp 后面的两个节点 node1 和 node2,通过更新节点的指针关系实现两两交换节点。

第二步:交换之前的节点关系是 temp -> node1 -> node2,交换之后的节点关系要变成 temp -> node2 -> node1

temp.next = node2
node1.next = node2.next
node2.next = node1
完成上述操作之后,节点关系即变成 temp -> node2 -> node1。

第三步:再令 temp = node1,对链表中的其余节点进行两两交换,直到全部节点都被两两交换。

两两交换链表中的节点之后,新的链表的头节点是 dummyHead.next,返回新的链表的头节点即可。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};


class Solution
{
public:
    ListNode *swapPairs(ListNode *head)
    {
        if (head == nullptr || head->next == nullptr)
        {
            return head;
        }
        ListNode *dummy = new ListNode(0, head);//创建一个哑节点,因为除了链表首两个节点交换,其他节点交换会有三个节点的指针发生变化。
        //所以为了不特殊处理头节点,创建了哑节点。

         ListNode *temp = dummy;

        while (temp->next != nullptr && temp->next->next != nullptr)//当哑结点的下一个元素和下两个元素都不为空的时候,才发生交换
        //
        {
            ListNode *one = temp->next;//第一个节点
            ListNode *two = temp->next->next;//第二个节点

            temp->next = two;
            one->next = two->next;
            two->next = one;
            temp = one;
        }

         ListNode *retrunhead= dummy->next;

        delete dummy;
        return retrunhead;
    }
};

题5:删除排序链表中重复的元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

示例 1:

输入:head = [1,1,2]
输出:[1,2]

思路:

本题可以用快慢指针的方法,将慢指针指向第一个节点,快指针指向第二个节点,如果第一个节点的值等于第二个节点的值,则删除当前节点,并移动快指针到下一个值,如果慢指针和快指针指向的值不相等,则慢指针和快指针都向前移动一次,直到遍历完链表。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

struct ListNode
{
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};


class Solution
{
public:
    ListNode *deleteDuplicates(ListNode *head)
    {
        if (head == nullptr || head->next == nullptr)//如果链表中没有元素或者只有一个元素则直接返回
        {
            return head;
        }

        ListNode *slow = head;
        ListNode *fast = head->next;

        while (fast != nullptr)
        {
            if (slow->val == fast->val)//如果慢指针的值等于快指针的值,则删除当前节点,慢指针的下一个节点指向要删除节点的下一个节点
            {
                slow->next = fast->next;
                fast = fast->next;//更新快指针为要删除节点的下一个节点
            }
            else //如果慢指针的值不等于快指针的值,则移动慢指针的值到下一位,移动快指针的值也到下一位
            {
                slow =slow->next;
                fast = fast->next;
            }
        }

        return head;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2235856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

28.医院管理系统(基于springboot和vue)

目录 1.系统的受众说明 2. 相关技术和开发环境 2.1 相关技术 2.1.1 Java语言 2.1.2 HTML、CSS、JavaScript 2.1.3 Redis 2.1.4 MySQL 2.1.5 SSM框架 2.1.6 Vue.js 2.1.7 SpringBoot 2.2 开发环境 3. 系统分析 3.1 可行性分析 3.1.1 经济可行性 3.1.2 技术…

Mysql基础 01 数据与sql

文章目录 一、基本概念二、mysql的常用命令三、sql规范四、数据类型五、SQL语句 一、基本概念 数据库(database,DB)&#xff1a;存储数据的仓库。 数据库管理系统软件(Database Management System,DBMS)&#xff1a;是一种操作和管理数据库的大型软件。常见的DBMS有oracle、s…

爬虫-------字体反爬

目录 一、了解什么是字体加密 二. 定位字体位置 三. python处理字体 1. 工具库 2. 字体读取 3. 处理字体 案例1:起点 案例2:字符偏移: 5请求数据 - 发现偏移量 5.4 多套字体替换 套用模板 版本1 版本2 四.项目实战 1. 采集目标 2. 逆向结果 一、了解什么是…

数据分析:宏基因组DESeq2差异分析筛选差异物种

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍原理:计算步骤:结果:加载R包准备画图主题数据链接导入数据Differential abundance (No BP vs 2BP TA)构建`countData`矩阵过滤低丰度物种构建DESeq数据对象DESeq2差异分析画图Di…

【手撕排序2】快速排序

&#x1f343; 如果觉得本系列文章内容还不错&#xff0c;欢迎订阅&#x1f6a9; &#x1f38a;个人主页:小编的个人主页 &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 ✌️ &#x1f91e; &#x1f91f; &#x1f918; &#x1f919; &#x1f448; &…

OpenCV自学系列(1)——简介和GUI特征操作

与另一个计算机视觉系列相对应&#xff0c;本系列主要探索OpenCV的具体操作。 学习资源&#xff1a;官网教程 https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.htmlhttps://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html 所有源码均上传至仓库&#xff1a; http…

KION Group EDI 需求分析

梳理EDI需求资料 KION Group将EDI项目中需要的资料公开在其官网上&#xff0c;企业可以点击在 KION Group 官网下载 EDI需求资料 企业可以在以上网址中获取到如下资料&#xff1a; 1.KION Group EDI团队的联系信息以及EDI连接信息 KION Group EDI团队支持7*24小时的支持&am…

OpenDroneMap Webodm

OpenDroneMap & Webodm OpenDroneMap Webodm 开源无人机航拍系列图像及其它系列图像三维重建软件。很棒的开源无人机测绘软件OpenDroneMap,从航拍图像生成精确的地图、高程模型、3D 模型和点云。 应用领域 Mapping & Surveying 测绘和测量 从图像测量获得高精度的可…

计算机网络——网络层导论

转发是局部功能——数据平面 路由是全局的功能——控制平面 网卡 网卡&#xff0c;也称为网络适配器&#xff0c;是计算机硬件中的一种设备&#xff0c;主要负责在计算机和网络之间进行数据传输。 一、主要功能 1、数据传输&#xff1a; 发送数据时&#xff0c;网卡将计算机…

从0开始深度学习(26)——汇聚层/池化层

池化层通过减少特征图的尺寸来降低计算量和参数数量&#xff0c;同时增加模型的平移不变性和鲁棒性。汇聚层的主要优点之一是减轻卷积层对位置的过度敏感。 1 最大汇聚层、平均汇聚层 汇聚层和卷积核一样&#xff0c;是在输入图片上进行滑动计算&#xff0c;但是不同于卷积层的…

深度学习笔记10-多分类

多分类和softmax回归 在多分类问题中&#xff0c;一个样本会被划分到三个或更多的类别中&#xff0c;可以使用多个二分类模型或一个多分类模型&#xff0c;这两种方式解决多分类问题。 1.基于二分类模型的多分类 直接基于二分类模型解决多分类任务&#xff0c;对于多分类中的每…

接口测试面试题及答案(后续)

一、你们什么时候测试接口 一般有需求就会做&#xff0c;后台的接口开发好&#xff0c;就可以开始测。例外&#xff0c;如果增加了新需求&#xff0c;也要做接口测试&#xff0c;还有就是开发对后台的接口做了修改&#xff0c;交互逻辑发生变化&#xff0c;我们也要重新对接口…

【SpringCloud】Nacos微服务注册中心

微服务的注册中心 注册中心可以说是微服务架构中的"通讯录"&#xff0c;它记录了服务和服务地址的映射关系 。在分布式架构中&#xff0c; 服务会注册到这里&#xff0c;当服务需要调⽤其它服务时&#xff0c;就从这里找到服务的地址&#xff0c;进行调用。 注册中心…

服务器数据恢复—分区结构被破坏的reiserfs文件系统数据恢复案例

服务器数据恢复环境&#xff1a; 一台服务器中有一组由4块SAS硬盘组建的RAID5阵列&#xff0c;上层安装linux操作系统统。分区结构&#xff1a;boot分区LVM卷swap分区&#xff08;按照顺序&#xff09;&#xff0c;LVM卷中划分了一个reiserfs文件系统作为根分区。 服务器故障…

Linux下的WatchDog

看门狗&#x1f415; 看门狗简介 看门狗定时器&#xff08;Watchdog Timer&#xff09;是一种定时器&#xff0c;用于检测系统是否正常运行。如果系统在规定时间内没有向看门狗定时器发送复位信号&#xff0c;看门狗定时器就会产生复位信号&#xff0c;使系统复位。看门狗定时…

机器学习(五)——支持向量机SVM(支持向量、间隔、正则化参数C、误差容忍度ε、核函数、软间隔、SVR、回归分类源码)

目录 关于1 间隔与支持向量2 对偶问题3 核函数4 软间隔与正则化5 支持向量回归6 核方法X 案例代码X.1 分类任务X.1.1 源码X.1.2 数据集&#xff08;鸢尾花数据集&#xff09;X.1.3 模型效果 X.2 回归任务X.2.1 源码X.2.2 数据集&#xff08;加州房价数据&#xff09;X.2.3 模型…

25国考照片处理器使用流程图解❗

1、打开“国家公务员局”网站&#xff0c;进入2025公务员专题&#xff0c;找到考生考务入口 2、点击下载地址 3、这几个下载链接都可以 4、下载压缩包 5、解压后先看“使用说明”&#xff0c;再找到“照片处理工具”双击。 6、双击后会进入这样的界面&#xff0c;点击&…

几个docker可用的镜像源

几个docker可用的镜像源 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; sudo rm -rf /etc/docker/daemon.json sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://d…

Vue 项目中为何选择 TSX 而非传统 .vue 文件

近年来&#xff0c;Vue 项目中使用 TSX&#xff08;TypeScript JSX&#xff09;的写法逐渐增多&#xff0c;尤其在 TypeScript 项目中。 1. TSX 与 Vue 的结合背景 1、Vue 3 和 TypeScript Vue 3 从设计之初便更好地支持 TypeScript。Vue 3 使用了 TypeScript 重写核心&…

【SpringBoot】18 上传文件到数据库(Thymeleaf + MySQL)

Git仓库 https://gitee.com/Lin_DH/system 介绍 使用 Thymeleaf 写的页面&#xff0c;将&#xff08;txt、jpg、png&#xff09;格式文件上传到 MySQL 数据库中。 依赖 pom.xml <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j --><depende…