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

题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;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

8.机器学习--决策树

(⊙﹏⊙)下周有要开组会&#xff0c;不知道该说啥&#xff0c;啊啊啊啊&#x1f62b; 目录 1.基本概念 2.ID3算法 3.C4.5算法 4.CART算法 5.连续与缺失值处理 5.1.连续值处理 5.2.缺失值处理 6.剪枝处理 6.1.预剪枝策略 6.2.后剪枝策略 7.实例代码 1.基本概念 提…

C#的6种常用集合类

一.先来说说数组的不足&#xff08;也可以说集合与数组的区别&#xff09;&#xff1a; 1.数组是固定大小的&#xff0c;不能伸缩。虽然System.Array.Resize这个泛型方法可以重置数组大小&#xff0c;但是该方法是重新创建新设置大小的数组&#xff0c;用的是旧数组的元素初始…

以太网交换安全:MAC地址漂移

一、什么是MAC地址漂移&#xff1f; MAC地址漂移是指设备上一个VLAN内有两个端口学习到同一个MAC地址&#xff0c;后学习到的MAC地址表项覆盖原MAC地址表项的现象。 MAC地址漂移的定义与现象 基本定义&#xff1a;MAC地址漂移发生在一个VLAN内的两个不同端口学习到相同的MAC地…

qt QHttpMultiPart详解

1. 概述 QHttpMultiPart是Qt框架中用于处理HTTP多部分请求的类。它类似于RFC 2046中描述的MIME multipart消息&#xff0c;允许在单个HTTP请求中包含多个数据部分&#xff0c;如文件、文本等。这种多部分请求在上传文件或发送带有附件的邮件等场景中非常有用。QHttpMultiPart类…

【SQL实验】高级查询(难点.三)含附加数据库操作

完整代码在文章末尾【代码是自己的解答&#xff0c;并非标准答案&#xff0c;也有可能写错&#xff0c;文中可能会有不准确或待完善之处&#xff0c;恳请各位读者不吝批评指正&#xff0c;共同促进学习交流】 将素材中的“学生管理”数据库附加到SQL SERVER中&#xff0c;完成以…

基于Spring Boot+Vue的助农销售平台(协同过滤算法、限流算法、支付宝沙盒支付、实时聊天、图形化分析)

&#x1f388;系统亮点&#xff1a;协同过滤算法、节流算法、支付宝沙盒支付、图形化分析、实时聊天&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk1…

std::copy

std::copy 是 C 标准库中的一个算法&#xff0c;用于将一个序列中的元素复制到另一个位置。这个算法定义在 <algorithm> 头文件中。 --- 函数原型 std::copy 有几个不同的重载版本&#xff0c;但以下是最常用的两个&#xff1a; template <class InputIterator, c…

Linux之sed命令详解

文章目录 &#x1f34a;自我介绍&#x1f34a;sed概述&#x1f34a;sed语法讲解格式&#xff1a;options 命令选项{commmand}[flags] &#x1f34a;场景训练 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff…

现代Web开发:React Hooks深入解析

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 现代Web开发&#xff1a;React Hooks深入解析 现代Web开发&#xff1a;React Hooks深入解析 现代Web开发&#xff1a;React Hook…

大语言模型(LLM)入门级选手初学教程 III

指令微调 一、指令数据的构建 包括任务描述&#xff08;也称为指令&#xff09;、任务输入-任务输出以及可选的示例。 Self-Instruct 指令数据生成&#xff1a;从任务池中随机选取少量指令数据作为示例&#xff0c;并针对Chat-GPT 设计精细指令来提示模型生成新的微调数据…

算法工程师重生之第四十六天(字符串接龙 有向图的完全可达性 岛屿的周长)

参考文献 代码随想录 一、字符串接龙 题目描述 字典 strList 中从字符串 beginStr 和 endStr 的转换序列是一个按下述规格形成的序列&#xff1a; 1. 序列中第一个字符串是 beginStr。 2. 序列中最后一个字符串是 endStr。 3. 每次转换只能改变一个字符。 4. 转换过程…

魅力标签云,奇幻词云图 —— 数据可视化新境界

目录 目的原理详解建议 标签云&#xff1a;用于汇总生成的标签&#xff0c;一般是独立词汇运行前的准备代码示例 词云&#xff1a;对本文中出现频率较高的词&#xff0c;视觉上突出显示总结 目的 掌握文本与文档可视化&#xff1a;使用特定软件或编程语言&#xff08;如Python…

云计算答案

情境一习题练习 一、选择题 1、在虚拟机VMware软件中实现联网过程&#xff0c;图中箭头所指的网络连接方式与下列哪个相关&#xff08; C &#xff09;。 A.仅主机模式 B.桥接 C.NAT D.嫁接 2、请问下图这个虚拟化架构属于什么类型&#xff08; A …

【测试】【Debug】vscode pytest 找不到测试用例测试文件 行号部位没有绿色箭头

出现这种情况首先检查&#xff1a; 是否安装pytest点击vscode的这个图标如果其中都是空的&#xff0c;没有识别上&#xff0c;并且写好的.py测试文件的行号前面没有运行符号&#xff0c;要检查名称是否按照pytest的要求写&#xff0c;不然会识别不到。 命名规则注意&#xff1…

Echats柱状图的横坐标用图片显示

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>图片作为横坐标示例 - ECharts</title><!-…

D-ID 推出能模仿用户的头部动作以及实时互动的 AI 头像

D-ID 宣布推出两种新型 AI 头像 — — Express 和 Premium&#xff0c;旨在提升内容创作的灵活性和人性化。这些头像将为企业在营销、销售和客户支持等领域的视频制作提供便利。用户只需少量文本输入和视觉数据&#xff0c;即可生成更自然的商业视频。 Express 头像可以通过约一…

vue使用canves把数字转成图片验证码

<canvas id"captchaCanvas" width"100" height"40"></canvas>function drawCaptcha(text) {const canvas document.getElementById(captchaCanvas);const ctx canvas.getContext(2d);// 设置背景颜色ctx.fillStyle #f0f0f0;ctx.f…

mybatisgenerator生成mapper时报错

本想使用generator自动生成model和mapper&#xff0c;没想到插件执行的时候报如下错误。 Failed to execute goal org.mybatis.generator:mybatis-generator-maven-plugin:1.3.2:generate (default-cli) on project ywq-mybatis-tools: Execution default-cli of goal org.myb…

【无标题】西安交通大学提出少锚点的端到端车道线检测算法Polar R-CNN

Abstract 车道线检测在自动驾驶中是一个关键且充满挑战的任务&#xff0c;特别是在实际场景中&#xff0c;由于车道线可能因其他车辆而被遮挡、形状纤细且长度较长&#xff0c;检测难度增大。现有基于锚点的检测方法通常依赖于预设的锚点来提取特征&#xff0c;并随后对车道线…

Vue + Vant Picker实现省市区三级联动

一、picker选择器的数据由columns属性控制&#xff0c;columns中有几个元素就代表该选择器有多少级&#xff0c;通过change方法来给对应列赋值 this.columns [{values: citys,className: "column1",defaultIndex: 0,flex: 1, //控制每列的宽度},{values: citys[0].…