【数据结构】顺序表和链表经典题目

news2024/11/15 1:51:52

系列文章目录

单链表
动态顺序表实现通讯录
顺序表


文章目录

  • 系列文章目录
  • 前言
  • 一、顺序表经典例题
    • 1. 移除元素
    • 2. 合并两个有序数组
  • 二、链表经典例题
    • 1. 移除链表元素
    • 2. 反转链表
    • 3. 合并两个有序链表
    • 4. 链表的中间节点
    • 5. 环形链表的约瑟夫问题
  • 总结


前言

我们通过前面对顺序表和链表的讲解,让大家对它们有了基本了解。下面是对于这两个知识点的一些经典例题的讲解,让大家更好熟悉它们。


提示:以下是本篇文章正文内容,下面案例可供参考

一、顺序表经典例题

1. 移除元素

题目链接:移除元素
题目要求:
题目要求
题目示例
题目的要求十分简单:将nums数组中val的值得元素移除,并返回移除后nums数组中剩余元素的大小。
题目解法:

  1. 暴力解法:我们看到这道题目自然就会想到一种暴力解法,我们从头开始遍历这个数组,当遇到元素值与val相等,将该元素后面的元素全部向前移动一位,然后再从该位置接着遍历判断,直到到数组末尾。------------------ 这种解法的代码很容易实现,这里不实现。对于这个解法,我们很清楚的看到将数组元素往前移这个做法十分浪费时间,时间复杂度只有O(N^2),那么我们怎么减少它的时间呢?
  2. 双指针解法:(这里我们第一次提到这种解法,解释一下:双指针解法是用两个指针解决问题,但这两个指针并不一定是指针变量,也可以整型变量等等,但能通过这两个变量指向某个地址,下面会有展示)----------------------------- 首先,我们肯定需要一个指针能遍历整个数组,还要一个指向符合要求的数组,由于数组可以通过[]访问元素,我们这里定义了两个int类型:front,low(front用来遍历),它们开始都指向第一个元素。
    在这里插入图片描述

在遍历时会遇到两种情况:1. front指向的元素不等于val值,这时我们让front指向的元素赋值给low指向的元素,然后front,low都++;2. front指向的元素等于val值,直接front++。通过这两幅图帮助大家理解为什么这么做就能完成。
在这里插入图片描述
在这里插入图片描述
最后,当front走到最后,low+1就是我们需要的k值。
代码示例:

int removeElement(int* nums, int numsSize, int val) {
    int front = 0;
    int low = 0;
    for(front = 0; front < numsSize; front++)
    {
        if(nums[front] != val)
        {
            nums[low] = nums[front];
            low++;
        }
    }
    return low;
}

2. 合并两个有序数组

题目链接:合并两个有序数组
题目要求:
在这里插入图片描述
在这里插入图片描述
题目解法:

  1. 暴力解法:定义两个int类型n1,n2分别指向nums1,nums2,有两种情况:1. n1指向的元素大于n2指向的元素,将n1指向的元素及其后面的后移一位,将n2指向的元素插入n1指向的位置,然后n1++,n2++;2. n1指向的元素小于等于于n2指向的元素,直接n1++,但l1到了m时,l2还没到末尾,直接将l2后面的值赋值到l1后面,直至n2到最后。----------------- 这种解法同样耗时,O(N^2)。
  2. 逆向三指针:暴力解法使用了双指针,但是是正向的,我们发现正向无法快速解决问题时,不妨思考逆向解决。l1指向nums1最后一个有效数据,l2指向nums2最后一个元素,l3指向nums1最后一个元素。
    在这里插入图片描述
    同样会遇到两种情况:1. l1和l2指向的元素那个更大,就让l3指向的元素等于更大的那个,相等则随便哪个;2. 当其中一个小于0时,则让l3指向的元素等于另一个,直至另一个小于0。
    在这里插入图片描述
    代码示例:
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
    int l1 = m - 1;
    int l2 = n - 1;
    int l3 = m + n - 1;

    while(l1 >= 0 && l2 >= 0)
    {
        if(nums1[l1] > nums2[l2])
        {
            nums1[l3] = nums1[l1];
            l1--;
            l3--;
        }
        else
        {
            nums1[l3] = nums2[l2];
            l2--;
            l3--;
        }
    }
    while(l2 >= 0)
    {
        nums1[l3] = nums2[l2];
        l2--;
        l3--;
    }
}

二、链表经典例题

1. 移除链表元素

题目链接:移除链表元素
题目要求:
在这里插入图片描述

题目解法:

  1. 暴力解法:我们遍历整个链表,当遇到与val相等的值,我们需要知道当前节点的上一个节点,并找到下一个不等于val的值得节点,时间复杂度只要O(N)。
  2. 新建链表:题目没有要求在该链表上修改,我们可以创建一个新链表,只需要将不等于val值得节点插入到该新链表即可,不需要得到该节点的上一个节点,回踩的坑比上面的解法更少。这个思路的代码很容易实现,我这直接给代码了。
    代码示例:
typedef struct ListNode Listnode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    Listnode* remove, * relast;
    remove = relast = NULL;

if (head == NULL)
	return NULL;
Listnode* pcur = head;
while (pcur)
{
	if (pcur->val == val)
	{
		pcur = pcur->next;
	}
	else
	{
		if (remove == NULL)
		{
			remove = relast = pcur;
			pcur = pcur->next;
		}
		else
		{
			relast->next = pcur;
			relast = pcur;
			pcur = pcur->next;
		}
	}
}
if (relast)
	relast->next = NULL;

return remove;
}

2. 反转链表

题目链接:反转链表
题目要求:
在这里插入图片描述

题目解法:
这题目没啥暴力解法,下面直接讲解我的使用方法:
三指针解法:我们要反转链表,就需要让当前节点的next指向它的上一个节点,同时要能找到它的下一个节点,所以需要三个指针。定义三个指针:prev,pcur,pnext。
prev指向当前节点上一个节点,pcur指向当前节点,pnext指向下一个节点。(我这里将参数head当做一个prev用,大家可以选择自己定义一个prev)
开始时,判断链表是否为空为空则返回NULL,否则pcur等于head,pnext = pcur->next,head->next = NULL,之后进入循环让pcur的next指向head,head等于pcur,pcur = pnext,pnext = pnext->next,直至pcur为空,返回pcur。
代码示例:

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* pcur = head;
    struct ListNode* pnext = NULL;

    if(pcur == NULL)
    return NULL;
    if(pcur->next == NULL)
    return head;
    pcur = pcur->next;
    pnext = pcur->next;
    head->next = NULL;
    while(pcur)
    {
        pcur->next = head;
        head = pcur;
        pcur = pnext;
        if(pnext != NULL)
        pnext = pnext->next;
    }
    return head;
}

3. 合并两个有序链表

题目链接:合并两个有序链表
题目要求:
在这里插入图片描述

题目解法:

  1. 暴力解法:我们可以选择一个作为主链表,将另一个链表的值插入到主链表中,这样需要不断调整指针指向,虽然思路简单,但容易踩坑。
  2. 新建链表:我们同样可以像上面一样,新建一个链表即可,将指针指向的值更小的那个节点插入到新链表,当一个指针指向NULL时,将另一个指针全部插入到新链表即可。这个代码同样容易实现,这里直接展示代码。
    我的代码中使用了哨兵位,可以使新节点第一个节点不用单独if判断,更便捷。
    代码示例:
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    ListNode *l1=list1;
    ListNode *l2=list2;
     ListNode*pos=( ListNode*)malloc(sizeof(ListNode));
     pos->next=NULL;
     if(l1==NULL&&l2==NULL){
        return list1;
     }
      ListNode *cur=pos;
     while(l1&&l2){

        if(l1->val<l2->val){
           cur->next=l1;
           cur=cur->next;
           l1=l1->next;

        }
        else{
            cur->next=l2;
           cur=cur->next;
           l2=l2->next; 
        }
     }
     if(l1){
        cur->next=l1;
     }
     if(l2){
        cur->next=l2;
     }
     ListNode *f=pos->next;
free(pos);
return f;
}

4. 链表的中间节点

题目链接:链表的中间节点
题目要求:
在这里插入图片描述

题目解法:

  1. 暴力解法:先遍历一遍链表,得到链表中有多少节点,然后节点 / 2,在从头开始找到该位置的节点即可。
  2. 快慢指针:暴力解法需要遍历两遍,这个解法只遍历一遍就得到结果。我们要找在中间的节点,那么我们只需要定义两个指针:pfast,plow,pfast每次走两个节点,plow每次走一个节点,当pfast为NULL时,plow不就指向中间节点了吗。
    代码示例:
struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* plow, *pfast;
    plow = pfast = head;

    while(pfast && pfast->next)
    {
        plow = plow->next;
        pfast = pfast->next->next;
    }
    return plow;
}

5. 环形链表的约瑟夫问题

题目链接:环形链表的约瑟夫问题
题目要求:
在这里插入图片描述

题目解法:
这道题可以使用顺序表,也可以使用链表解决。
对于顺序表,也可定义要求大小的数组并依次赋值,将报数为m的元素赋值为0,循环遍历时,遇到0就跳过,当到数组末尾时,让其重新指向数组开头。
这里我们提供链表的代码,上面的数组提供一种思路。
链表解法:我们创建一个符合题目要求的循环单链表,遇到报数为m的节点,将该节点删除,然后接着报数,当该节点的next指向自己时或计数器为1时,就是最后留下的人的编号。当然,大家也可以选择将报数为m的节点不删除,修改其值即可,与顺序表操作类似,但是链表的删除不需要移动其他节点,并不消耗什么时间。
代码示例:

typedef struct person
{
    int num;
    struct person* next;
}Per;
void cirlist(Per** pphead, int n)
{
    Per* pcur = *pphead;
    int i = 0;
    while (n--)
    {
        Per* newnode = (Per*)malloc(sizeof(Per));
        if (newnode == NULL)
        {
            perror("malloc");
            exit(1);
        }
        newnode->num = ++i;
        newnode->next = NULL;
        if (*pphead == NULL)
        {
            *pphead = pcur = newnode;
        }
        else {
            pcur->next = newnode;
            pcur = newnode;
        }
    }
    pcur->next = *pphead;
}
int count(Per* phead, int m, int n)
{
    Per* prev = phead;
    Per* pcur = phead;
    Per* next = phead->next;
    int i = 0;
    while (n != 1)
    {
        i++;
        if (i == m)
        {
            prev->next = next;
            free(pcur);
            pcur = NULL;
            n--;
            i = 0;
        }
        else {
            prev = pcur;

        }
        pcur = next;
        next = next->next;
    }
    return prev->num;
}
int ysf(int n, int m) {
    // write code here
    Per* phead = NULL;
    cirlist(&phead, n);
    int number = count(phead, m, n);
    return number;
}

总结

这篇博客,我们讲解了两道顺序表题和五道链表题,最后一道使用了循环链表,希望大家能通过这几道题对顺序表和链表有更深的了解。

谢谢大家观看!!!

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

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

相关文章

NVM(node.js版本工具)的使用

1.nvm是什么 NVM 是 Node Version Manager 的缩写&#xff0c;它是一个用于管理 Node.js 版本的命令行工具。通过NVM&#xff0c;你可以在同一台机器上安装和切换多个 Node.js 版本&#xff0c;对于开发和测试在不同 Node.js 版本上运行的应用程序非常有用。 2.下载 下载之前…

『功能项目』眩晕图标显示【52】

我们打开上一篇51调整Boss技能bug的项目&#xff0c; 本章要做的事情是在释放法师的眩晕技能时&#xff0c;boss01处在眩晕动画时显示一个眩晕图标 首先双击Boss01预制体进入预制体空间 创建一个Image重命名为StateUIdiz 代表第一个受击状态 设置Canavas 并且修改Canvas的渲染…

Java 学习全攻略:从入门到精通的详细指南

目录 一、引言 Java 的背景和发展 学习 Java 的意义 二、Java 的核心特性 1. 面向对象编程&#xff08;OOP&#xff09; 2. 跨平台性 3. 自动内存管理 4. 强大的标准库 三、Java 基础语法 1. 变量和数据类型 原始数据类型 引用数据类型 2. 运算符 3. 控制结构 条…

柳淘鸿黄金沁透发热面膜:肌肤逆龄之旅的秘密武器!

柳淘鸿黄金沁透发热面膜&#xff1a;肌肤逆龄之旅的秘密武器&#xff01;"柳淘鸿的黄金沁透发热面膜液融合了中国发明专利,专利号:ZL202310228041.5对应成分:胶原, 金&#xff0c;珍珠粉以及多种珍贵植物萃取精华&#xff0c;是肌肤逆龄之旅的绝密武器。这款面膜液温和滋养…

Git之误执行git rm -r解决方案(六十七)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

视频分割操作教程

1、打开剪映 2、点击开始创作上面的“”&#xff0c;选择视频&#xff0c;点击添加按钮&#xff0c;导入一个视频素材到剪映 3、滑动视频&#xff0c;让视频竖线到合适位置 4、点击视频&#xff0c;出现白色边框 5、点击工具栏“分割”&#xff0c;然后点击需要删除的视频部分 …

03 战略的本质与实践 - 战略管理实践的启示

1,战略有一定复杂性。在学术界就有很多学派。明斯伯格,加拿大的管理学家,认为有10大学派。 我看来有三个方面: 理性学派:通过规划、主动管理 演进学派:根据一个不确定性的环境,自发自下而上来形成 过程管理:重要的不是结果,而是过程 简单的看,如下图。 显示公司自己的…

查询分类数据序列中的每个类别 Series.cat.categories

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 1.定义分类序列s 2.查询s中的各类别 Series.cat.categories 选择题 关于以下代码输出结果的说法中正确的是? import pandas as pd s pd.Series([a,b,a,b], dtypecategory) print("【…

windows系统安装docker

参考&#xff1a;GitHub - tech-shrimp/docker_installer: Docker官方安装包&#xff0c;用来解决因国内网络无法安装使用Docker的问题 1.windows系统安装docker cmd 右键 以管理员身份运行 输入 wsl --set-default-version 2 wsl --update --web-download GitHub - tech-s…

系统架构设计师教程 第5章 5.2 需求工程 笔记

5.2 需求工程 ★★★★★ 软件需求是指用户对系统在功能、行为、性能、设计约束等方面的期望。 软件需求包括3个不同的层次&#xff1a;业务需求、用户需求和功能需求(也包括非功能需求)。 (1)业务需求 (business requirement) 反映了组织机构或客户对系统、产品高层次的目标…

Flutter启动无法运行热重载

当出现这种报错时&#xff0c;大概率是flutter的NO_Proxy出问题。 请忽略上面的Android报错因为我做的是windows开发这个也就不管了哈&#xff0c;解决下面也有解决报错的命令大家执行一下就行。 着重说一下Proxy的问题&#xff0c; 我们看到提示NO_PROXY 没有设置。 这个时候我…

掌握ZooKeeper的业务使用场景,ZooKeeper如何实现分布式锁

1. ZooKeeper分布式锁 1.1 排他锁实现分布式锁 面试官&#xff1a;知道Zookeeper有什么应用场景吗? 目前地球村里大型公司部署的分布式技术&#xff0c;绝大部分都是由Zookeeper提供底层的技术支持&#xff0c;所以Zookeeper多么重要就不用我多说了吧。 我们可以利用Zookeep…

多态(下)【C++】

抽象类 抽象类的定义 只要有纯虚函数的类就是抽象类 什么是纯虚函数&#xff1f; 纯虚函数是一种特殊的虚函数&#xff0c;它是没有函数体的虚函数 纯虚函数的语法&#xff1a; class <类名> { public:virtual <类型><函数名>(<参数表>) 0; };…

基于 jenkins 的持续集成、持续部署方案

工具介绍 python3.12 fastapi 0.92.0 uvicorn 开发部署web项目&#xff1b;git gitee 实现代码版本管理&#xff1b;jenkins docker 实现持续集成、持续部署&#xff1b;centos7 作为jenkins服务器 & 部署服务器&#xff1b;有条件的可以再启动一台服务器作为部署测试…

【Elasticsearch系列六】系统命令API

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

‌内网穿透技术‌总结

内网穿透是一种网络技术&#xff0c;通过它可以使外部网络用户访问内部网络中的设备和服务。一般情况下&#xff0c;内网是无法直接访问的&#xff0c;因为它位于一个封闭的局域网中&#xff0c;无法从外部访问。而通过内网穿透&#xff0c;可以将内部网络中的设备和服务暴露在…

2024年TCGA基因表达数据下载(最新版)

文章目录 前言一、如何使用TCGA数据库获取公共数据?二、使用步骤1.点击Cohort Builder2.数据筛选3. Repository4.数据下载4.1 继续选择筛选条件4.2 添加cart并进入4.3 下载 总结 前言 TCGA 全称 The Cancer Genome Atlas &#xff0c;即癌症基因组图谱。它是一个大型的癌症研…

【Python篇】深度探索NumPy(下篇):从科学计算到机器学习的高效实战技巧

文章目录 Python NumPy学习指南前言第六部分&#xff1a;NumPy在科学计算中的应用1. 数值积分使用梯形规则进行数值积分使用Simpson规则进行数值积分 2. 求解微分方程通过Euler方法求解一阶常微分方程使用scipy.integrate.solve_ivp求解常微分方程 3. 随机过程模拟模拟布朗运动…

Linux下进程间的通信--共享内存

共享内存概述&#xff1a; 共享内存是进程间通信的一种方式&#xff0c;它允许两个或多个进程共享一个给定的存储区。共享内存是最快的一种IPC形式&#xff0c;因为它允许进程直接对内存进行读写操作&#xff0c;而不需要数据在进程之间复制。 共享内存是进程间通信&#xff…

【C++】【网络】【Linux系统编程】单例模式,加锁封装TCP/IP协议套接字

目录 引言 获取套接字 绑定套接字 表明允许监听 单例模式设计 完整代码示例 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 引言 有关套接字编程的细节和更多的系统调用课参考《UNIX环境高级编程》一书&#xff0c;可以在如下网站搜索电子版&#xff0c;该书在第16章详…