常见的链表的OJ题

news2024/9/22 0:40:06

  在本次的博客当中,为了巩固关于链表技能的运用,我们先来看一些与链表有关的OJ题。

    🌵反转链表

   题目详情如下:

     第一道题目从逻辑上看不难,我们只需要将链表进行拆分,将我们下一个节点进行一个类似于头插的操作即可。需要我们注意的是我们需要创建一个指针变量接收我们的下一个节点的地址之后才可以进行节点下一个位置的覆盖,否则就会造成节点的遗失。

  所进行编写的代码如下:

struct ListNode* reverseList(struct ListNode* head)
{
    if(head==NULL)
        return head;
    if(head->next==NULL)
        return head;
    //接收头节点的指针
    struct ListNode*next=head->next;
    //接收拆下链表的节点
    struct ListNode*prev=head;
    //
    struct ListNode*ret=next;
    head->next=NULL;
    while(next)
    {
        next=next->next;
        ret->next=prev;
        prev=ret;
        if(next!=NULL)
        {
            ret=next; 
        }
    }
    return ret;
}

    🌵链表的中间节点

  本题目需要我们使用一些小技巧,比如说快慢指针,我们需要设置两个指针,均从链表的头部开始。一个慢指针一次走一步,一个快指针一次走两步,我们最后得到的就是链表的中间节点。(当我们链表当中的数据为奇数个的时候,满指针所指向的是中间的节点,快指针指向的是中间靠后的一个节点)

   就像是我们上面的构思图所示,我们只需要判断我们fast节点的下一位是否为NULL就可以判断链表是否已经遍历结束。但是我们会发现当我们的链表节点个数为偶数的时候fast指针最后会指向NULL,如果这个时候将NULL作为地址判断next的话就会产生空指针的非法使用的问题。我们可以分开判断一次判断fast即可。所示的代码如下:

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode*before=head;
    struct ListNode*next=head;
    while(next)
    {
         next=next->next;
        if(next==NULL)
        {
            return before;
        }
        else
        {
            next=next->next;
        }
        before=before->next;
    }
    return before;
}

    🌵链表倒数第k个结点 

  这道题看起来很难可是思路却和我们判断链表的中间节点很类似。我们同样需要设置一个前后指针。我们想要寻找倒数第k个节点就可以先让一个指针走k-1步之后让另一个指针从开始的头节点开始走,最后当先走的那个指针指向NULL的时候,我们后走的指针得到的就是倒数第K个位置的指针。

   我们循环的结束条件可以设置为我们的first指针的next为NULL。按照我们上述思路可以书写出以下的代码:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{
    // write code here
    //判断节点为空
    if(pListHead==NULL)
    {
        return NULL;
    }
    struct ListNode*behind=pListHead;
    struct ListNode*next=pListHead;
    //后节点先走k-1步
    k--;
    while(k--)
    {
        if(next->next==NULL)
        {
            return NULL;
        }
        next=next->next;
    }
    while(next->next!=NULL)
    {
        behind=behind->next;
        next=next->next;
    }
    return behind;
}

    🌵合并两个有序链表

 

   想要合并两个链表其实很简单我们只需要将我们两个链表进行分别的拆分,之后比较大小,将较小的一方拼接入新的链表当中。(为了是我们节点拼接更加方便,我们可以后才能构建一个头节点,将我们查下来的节点链接进入我们头节点的后面即可)所示代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* ret1 = list1;
    struct ListNode* ret2 = list2;
    struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* first = newnode;
    while (ret1 && ret2)
    {
        if (ret1->val >= ret2->val)
        {
            newnode->next = ret2;
            ret2 = ret2->next;
            newnode = newnode->next;
        }
        else
        {
            newnode->next = ret1;
            ret1 = ret1->next;
            newnode = newnode->next;
        }
    }
    //将另一个没有处理完的链表链入我们的合并链表当中
    if (ret1 == NULL)
    {
        newnode->next = ret2;
    }
    else
    {
        newnode->next = ret1;
    }
    struct ListNode* ret = first->next;
    free(first);
    first = NULL;
    return ret;
}

    🌵 链表分割

 

   阅读我们题目中的要求我们可以知道,我们需要进行的操作就是将我们链表当中的数据进行分裂,大于等于特定值的归为一类,小于特定值的归为另一类。我们可以创建两个节点,将我们较小的放入lower节点后面,将我们较大的放入bigger节点后面。之后再将lower和bigger两个链表拼接即可得到我们目标的链表。

    经过上面的梳理我们可以写出以下的代码:

ListNode* partition(ListNode* pHead, int x) 
    {
        // write code here
        //开辟两个新的结构体变量
        //将我们的大于或者小于定值的节点放到指定位置后面
        struct ListNode*bigger=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode*lower=(struct ListNode*)malloc(sizeof(struct ListNode));
       //创建三个结构体指针变量
       //分别作为我们遍历链表,后面释放开辟好的节点进行使用
       struct ListNode*HeadBigger=bigger;
       struct ListNode*HeadLower=lower;
       struct ListNode*ret=pHead;
       //开始遍历链表
       while(ret)
       {
            if(ret->val<x)
            {
                lower->next=ret;
                lower=lower->next;
            }
            else 
            {
                bigger->next=ret;
                bigger=bigger->next;
            }
            ret=ret->next;
        }
        lower->next=HeadBigger->next;
        bigger->next=NULL;
        struct ListNode*retu=HeadLower->next;
        free(HeadBigger);
        free(HeadLower);
        return retu;
    }

    🌵 链表的回文结构

 

  要是学过栈结构的朋友们一看到这个题目都会想到,我们可以使用栈呀!就和我们括号的判断一样。但是对于栈我们还没有进行讲解,所以我们先来使用一些技巧进行进行本道题目的讲解:

  要想判断我们的链表是否为回文结构,我们只需要从中间节点开始判断即可。因为假如符合我们的回文结构的话前面的节点的内容和我们后面节点当中的内容是完全逆置的。所以对于本题我们需要进行的操作为:先找到来链表当中的中间节点,之后将我们的后半部分链表进行逆序,最后在判断我们前后链表的结构是否相同(可以剩下一个节点不做判断,即当我们节点的个数为奇数个时,我们只需要知道一个链表为空即为链表的遍历结束。)

  所编写的代码如下:

struct ListNode* reverseList(struct ListNode* head)
    {
        if(head==nullptr)
            return head;
        if(head->next==nullptr)
            return head;
        //接收头节点的指针
        struct ListNode*next=head->next;
        //接收拆下链表的节点
        struct ListNode*prev=head;
        //
        struct ListNode*ret=next;
        head->next=nullptr;
        while(next)
        {
            next=next->next;
            ret->next=prev;
            prev=ret;
            if(next!=nullptr)
            {
                ret=next; 
            }
        }
        return ret;
    }

    struct ListNode* middleNode(struct ListNode* head)
    {
        struct ListNode*before=head;
        struct ListNode*next=head;
        while(next)
        {
            next=next->next;
            if(next==nullptr)
            {
                return before;
            }
            else
            {
                next=next->next;
            }
            before=before->next;
        }
        return before;
    }

    bool chkPalindrome(ListNode* phead)
{
    // write code here
    ListNode* mid = middleNode(phead);
    ListNode* rmid = reverseList(mid);
    while (phead && rmid)
    {
        if (phead->val != rmid->val)
        {
            return false;
        }
        phead = phead->next;
        rmid = rmid->next;
    }
    return true;
}

  其中的查找中间节点的函数和逆序函数复用我们之前实现好的即可。

    🌵 相交链表

  这道题相比于我们之前的题目来说简单很多。我们观察一下相交链表的特点我们会发现:我们的链表一旦相交那么最后一个节点的地址一定相同,否则最后一个节点的值就不同。我们可以利用这一特点很简单的编写我们的代码。我们可以对这两个链表分别进行遍历,找到最后的节点,然后判断两个节点的地址是否相同即可。所示代码如下:

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)
{
    //count代表的是两个链表的节点个数之差
    struct ListNode* s1 = headA;
    struct ListNode* s2 = headB;
    while (s1->next)
    {
        s1 = s1->next;
    }
    while (s2->next)
    {
        s2 = s2->next;
    }
    if (s1 != s2)
    {
        return NULL;
    }
    int count = 0;
    struct ListNode* ret1 = headA;
    struct ListNode* tmp1 = headA;
    struct ListNode* ret2 = headB;
    struct ListNode* tmp2 = headB;
    while (ret1 && ret2)
    {
        ret1 = ret1->next;
        ret2 = ret2->next;
    }
    if (ret1 == NULL)
    {
        while (ret2)
        {
            ret2 = ret2->next;
            count++;
        }
        while (count--)
        {
            tmp2 = tmp2->next;
        }
    }
    else
    {
        while (ret1)
        {
            ret1 = ret1->next;
            count++;
        }
        while (count--)
        {
            tmp1 = tmp1->next;
        }
    }
    while (tmp1 != tmp2)
    {
        tmp1 = tmp1->next;
        tmp2 = tmp2->next;
    }
    return tmp1;
}

    🌵 环形链表

   链表带环问题其实就是一个追击相遇问题。我们只需要使用我们前面说到过的快慢指针的方法即可。 我们可以让我们的一个指针每次先走一步,另一个指针每次走两步。如果链表带环的话那么我们的快指针最后一定会追上我们的慢指针。我们利用上述的思路可以编写如下的代码:

bool hasCycle(struct ListNode* head)
{
    if (head == NULL)
    {
        return false;
    }
    if (head->next == head)
    {
        return true;
    }
    struct ListNode* before = head;
    struct ListNode* behind = head;
    while (behind)
    {
        before = before->next;
        behind = behind->next;
        if (behind == NULL)
        {
            return false;
        }
        else
        {
            behind = behind->next;
        }
        if (behind == before)
        {
            return true;
        }
    }
    return false;
}

    🌵 循环链表2

  我们本道链表带环问题可以说是我们上一道题目的plus版。我们不仅需要判断链表是否带环,还需要返回我们刚进入环内的节点。对于本题我们可以采用一些数学上的逻辑来进行推断:

   经过我们上面的推导之后我们就得出了n*C-N=L,也就是说我们从开始到循环节点的路程等于我们走整圈的数量减去我们从开始循环节点到我们相遇节点的距离。那么假如我们提前将这个N走完了,也就是说只要我们从相遇节点出发两个指针一旦相遇那么相遇节点一定就是我们进入循环的节点了呢?根据我们上面所推得的结论我们可以编写以下代码:

struct ListNode *detectCycle(struct ListNode *head) 
{
    //判断相遇的节点
    struct ListNode*fast=head;
    struct ListNode*slow=head;
    struct ListNode*meetnode=NULL;
    struct ListNode*retu=NULL;
    if(head==NULL)
    {
        return NULL;
    }
    while(fast&&slow)
    {
        fast=fast->next;
        if(fast==NULL)
        {
            return NULL;
        }
        else
        {
            fast=fast->next;
        }
        slow=slow->next;
        if(fast==slow)
        {
            meetnode=fast;
            break;
        }
    }
    //一个节点从头开始走,一个节点从相遇节点开始走,最后在循环节点相遇
    struct ListNode*begin=head;
    if(head==meetnode)
    {
        return head;
    }
    while(begin&&meetnode)
    {
        begin=begin->next;
        meetnode=meetnode->next;
        if(begin==meetnode)
        {
            retu=begin;
            return begin;
        }
    }
    return NULL;
}

    🌵复制带随机指针的链表

  最后两道题可能是我们本次博客最难的两道题目了,只要掌握好了这两道题相信我们对于链表的理解已经很深刻了。接下来我们来攻破最后一道难关。

  对于这一道题目如果没有一个清晰的思路我们会感到很头疼,接下来我来向大家介绍一种思路:
我们想要复制上面的代码可以尝试着创建相等数量的节点,最后将我们每一个节点链接到我们相应节点的后面,之后将我们的random指针指向我们原链表当中节点的下一个即可。

   唯一需要我们特别注意的是我们需要特别判断我们的random指向null的情况,因为指向空在进行next引用的话就会产生空指针的非法引用的问题。

  当我们来链接好我们的新建链表之后我们只需要在再进行链表拆分,拆下我们拼接上的节点即可。经过我们上面的思路我们可以编写如下的代码:

struct Node* BuyNewNode(int data)
{
    struct Node* newnode = (struct Node*)malloc(sizeof(struct Node));
    newnode->val = data;
    newnode->next = NULL;
    return  newnode;
}

struct Node* copyRandomList(struct Node* head) {
    //在每一个节点的后面创建一个新的节点,用于生成新的链表
    if (head == NULL)
    {
        return NULL;
    }
    if (head->next == NULL)
    {
        struct Node* ret1 = (struct Node*)malloc(sizeof(struct Node));
        if(head->random==NULL)
        {
            ret1->random=NULL;
        }
        else
        {
            ret1->random=ret1;
        }
        ret1->val = head->val;
        ret1->next = head->next;
        return ret1;
    }
    struct Node* tmp = head;
    while (tmp)
    {
        struct Node* newnode = BuyNewNode(tmp->val);
        //保存链表当中下一个节点的地址
        struct Node* ret = tmp->next;
        tmp->next = newnode;
        newnode->next = ret;
        tmp = ret;
    }
    //将节点中的随机值复制进入新的节点
     tmp = head;
    while (tmp)
    {
        if (tmp->random==NULL)
        {
            tmp->next->random = NULL;
        }
        else
        {
            tmp->next->random = tmp->random->next;
        }
        tmp = tmp->next->next;
    }
    //将创建好的新的链表进行拆解
    //跳过原链表当中的节点
    tmp = head->next;
    struct Node* retu = head->next;
    struct Node* newlist = tmp;
    while (tmp)
    {
        newlist->next = newlist->next->next;
        tmp = newlist->next->next;
        newlist = newlist->next;
    }
    newlist->next = NULL;
    return retu;
}

   以上就是我们本次博客的主要内容了,感谢您的观看,再见。

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

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

相关文章

【Java 数据结构】Map和Set

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

35岁程序员被裁赔偿27万,公司又涨薪让我回去,前提是退还补偿金,能回吗?

在大多数人眼里&#xff0c;35岁似乎都是一道槛&#xff0c;互联网界一直都有着“程序员是吃青春饭”的说法&#xff0c;。 如果在35岁的时候被裁能获得27万的赔偿&#xff0c;公司又涨薪请你回去上班&#xff0c;你会怎么选&#xff1f; 最近&#xff0c;就有一位朋友在网上…

Linux安装miniconda3

下载Miniconda&#xff08;Python3版本&#xff09; 下载地址&#xff1a;https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 安装Miniconda&#xff08;需要连网&#xff09; &#xff08;1&#xff09;将Miniconda3-latest-Linux-x86_64.sh上传到/o…

研读Rust圣经解析——Rust learn-14(面向对象)

研读Rust圣经解析——Rust learn-14&#xff08;面向对象&#xff09; Rust面向对象对象包含数据和行为封装继承多态 实现面向对象书写最外层逻辑userServiceUser Rust面向对象 在一些定义下&#xff0c;Rust 是面向对象的&#xff1b;在其他定义下&#xff0c;Rust 不是 对象…

算法刷题|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组

最大递增子序列 题目&#xff1a;给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6…

c++文件操作Ofstream、Ifstream,如何获取文件长度

一、文件光标定位streampos 在读写文件时&#xff0c;有时希望直接跳到文件中的某处开始读写&#xff0c;这就需要先将文件的读写指针指向该处&#xff0c;然后再进行读写。 ifstream 类和 fstream 类有 seekg 成员函数&#xff0c;可以设置文件读指针的位置&#xff1b;ofstr…

OpenGL光照:颜色

知识点归纳 现实世界中有无数种颜色&#xff0c;每一个物体都有它们自己的颜色。我们要做的工作是使用(有限的)数字来模拟真实世界中(无限)的颜色&#xff0c;因此并不是所有的现实世界中的颜色都可以用数字来表示。然而我们依然可以用数字来代表许多种颜色&#xff0c;并且你甚…

autosar

一 autosar简介 AUTOSAR&#xff0c;汽车开放系统架构&#xff08;AUTomotive Open System Architecture&#xff09;是一家致力于制定汽车电子软件标准的联盟。AUTOSAR是由全球汽车制造商、部件供应商及其他电子、半导体和软件系统公司联合建立&#xff0c;各成员保持开发合作…

QT DAY2

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setFixedSize(600,600); //设置固定尺寸this->setWindowTitle("汪玉洁大聪明")…

Hadoop学习笔记(一)Hadoop的组成

1. HDFS NameNode用于记录整个数据的存储情况&#xff0c;具体的数据存储在各个Hadoop节点中&#xff0c;每个Hadoop的节点可以称为DataNode。假设Hadoop1到Hadoop100的机器每个都有1T的容量。那么一共就可以存储100T的数据。 NameNode(nn)&#xff1a;存储文件的元数据&…

位运算【巧妙思路、两种常见题型】

这里介绍两种代码中位运算非常常用的操作 n的二进制表示中第k位数——右移操作 &1 例如说&#xff0c;我们需要计算11的第2位数。 11 (1011)2 我们常规思路就是将其转化为二进制数后&#xff0c;直接观察对应位置的值 这里需要注意的是第k位数指的是从右开始的第k位&a…

Linux shell编程 条件语句

条件测试 test命令 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;否则返回其他数值 格式1: test 条件表达式 格式2: [ 条件表达式 ]文件测试 [ 操作符 文件或者目录 ][ -e 1.txt ]#查看1.txt是否存在&#xff0c;存在返回0 echo $? #查看是上一步命令执行结果 0成…

DJ4-3 连续分配存储管理方式

目录 4.3.1 单一连续分配 4.3.2 固定分区分配 1. 分区说明表 2. 内存分配过程 4.3.3 动态分区分配 一、分区分配中数据结构 二、分区分配算法 三、分区分配操作 4.3.4 可重定位分区分配 1. 紧凑 2. 动态重定位 3. 动态重定位分区分配算法 连续分配是指为用户程…

【数据结构】堆(一)

&#x1f61b;作者&#xff1a;日出等日落 &#x1f4d8; 专栏&#xff1a;数据结构 如果我每天都找出所犯错误和坏习惯&#xff0c;那么我身上最糟糕的缺点就会慢慢减少。这种自省后的睡眠将是多么惬意啊。 目录 &#x1f384;堆的概念及结构&#xff1a; &#x1f384;堆的实…

万丈高楼平地起 AI帮你做自己

AI的自我介绍 AI是人工智能&#xff08;Artificial Intelligence&#xff09;的英文缩写&#xff0c;是一种通过计算机技术模拟和延伸人类智能的技术和应用。AI可以被看作是一种智能化的计算机程序或系统&#xff0c;它能够自动地执行一些需要人类智能才能完成的任务&#xf…

JavaEE初阶学习:初识网络

1.网络发展史 1.独立模式 独立模式:计算机之间相互独立&#xff1b; 2.网络互连 随着时代的发展&#xff0c;越来越需要计算机之间互相通信&#xff0c;共享软件和数据&#xff0c;即以多个计算机协同工作来完成业务&#xff0c;就有了网络互连。 网络互连&#xff1a;将多…

除了Figma,再给你介绍10款好用的协同设计软件

组织结构越来越复杂&#xff0c;团队中的每个人都有独特的技能、经验和专业知识。我们怎样才能让团队更好地合作&#xff1f;在这种情况下&#xff0c;协同设计应运而生。 UI的未来是协同设计&#xff01;如果你想把握未来的设计趋势&#xff0c;不妨从使用高效的协同设计软件…

Docker的安装以及本地部署ILLA Builder

1.安装Docker&#xff0c;当前版本V4.18.0 。Docker引擎启动运行之后&#xff0c;效果如下图&#xff08;喜欢暗黑主题&#xff09; Docker启动可能出错&#xff0c;“Docker Desktop requires a newer WSL kernel version.” 如下图所示 解决方法&#xff0c;比较简单&#xf…

测试用例的基本要素和设计方法

作者&#xff1a;爱塔居 专栏&#xff1a;软件测试 作者简介&#xff1a;大三学生&#xff0c;希望同大家一起进步&#xff01; 文章简介&#xff1a;介绍写测试案例的功能需求测试和非功能需求测试和具体方法&#xff1a;判定表、正交表、等价类、边界值等 文章目录 目录 文章…

if条件语句

if条件语句 条件测试 test 测试表达式是否成立&#xff0c;若成立返回0&#xff0c;否则返回其他数值 格式1 &#xff1a;test 条件表达式&#xff1b;格式2 &#xff1a;[ 条件表达式 ] echo $?参数作用-d测试是否为目录 (Directory)-e测试目录或文件是否存在(Exist)-f测…