顺序表和单链表的经典算法题

news2024/12/25 9:31:02

目录

前言

一、基础思想(数组)

1. 移除元素

 2.删除有序元素的重复项

 3.合并两个有序数组

二、单链表算法

1.移除链表元素 

2.翻转链表

 3.合并两个有序的链表


 

前言

Hello,小伙伴们,今天我们来做一个往期知识的回顾,今天我将为大家讲解几道经典的顺序表和单链表算法题,来帮助大家加深对单链表知识的讲解,同时带领大家来感受一下数据结构的魅力!

如果你喜欢我的内容的话,就请不要忘了点赞,评论和收藏,你的支持就是我更新的动力,万分感谢!!

好废话不多说,开始我们今天的正题。

一、基础思想(数组)

1. 移除元素

题目链接:https://leetcode.cn/problems/remove-element/description/

我们先看这道题假设我们现在一这样的一个数组:

要让我们清除掉所有 val = 3的值,我们想一想可以怎么做呢?

1.首先我们最容易想到的就是创建一个新的数组,然后遍历整个nums,将val != 4的值都放进 

新的数组中,但是我们要注意题目中的条件,“你需要原地移除数字==val的值

也就是说,我们不能开辟新的空间。所以这个方法行不通!

2.那我们可以试试这样的方法,我们首先创建两个整型变量,d1 ,d2 = 0;

初始的状态下,是这样的

我们让d1先走,当 nums[d1] != val时

nums[d2] = nums[d1];

d1++;

d2++;

然后再当nums[d1] == val时,直接跳过该元素,后面的元素会将其覆盖掉,或者是直接失去他的访问权限;

我们画图理解一下:

当nums[d1] == val时,d1直接跳过该元素,直到nums[d1] != val 或者 走到数组的末尾。

接下来直接直接拷贝到d2上:

d2++;

nums[d2] = nums[d1]

 

同理:最后我们就可以得到去除掉val的数组了,然后我们来实现一下这个代码:

int removeElement(int* nums, int numsSize, int val) {
    int dest = 0;
    int src = 0;
    for (int i = 0; i < numsSize; i++)//遍历数组
    {
        if (nums[src] == val)//排除指定的元素
        {
            src++;
        }
        else//计数
        {
            nums[dest] = nums[src];
            dest++;
            src++; 
        }
    }
    return dest;
}

 测试结果:下面是我根据这个思路对代码进行优化的结果,感兴趣的小伙伴一定要勇于挑战自己啊!!

 2.删除有序元素的重复项

题目链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/

 

这样的题,遇到了,我们能想出什么样的思路能?

假设我们有这样的一个 数组:

想要在题目中的条件下删除所有的重复项元素,我们能怎么办呢?

 有了上一道题的基础,我们不 难想到,先创建两个整型变量:
int d1 = 0; int d2 =  0;

我们让d1先加1

再比对nums[d1]与nums[d2];

不相等则 nums[++d2] = num[d1],然后 d1++,直到整个循环备d1遍历一遍!

好,接下来我们来实现一下代码:

int removeDuplicates(int* nums, int numsSize) {
   int dest = 0;
   int src = 1;
   while (src < numsSize)
   {
    if (nums[dest] != nums[src] && ++dest != src)   
    {
//++dest != src 可以避免相等项的重复赋值!! 提高效率
        nums[dest] = nums[src];
    }
    src++;
   }
    return dest + 1;

}

 代码测试:

 3.合并两个有序数组

题目链接:https://leetcode.cn/problems/merge-sorted-array/description/

这道题非常的有意思:

 假设我们得到这两个数组:

要注意最后的数组是,经过nums1修改后得到的,返回的数组也是nums1,不能开辟新的空间!!! 

我们可以试试这样的思路:

当 n and m都不小于0的时候,我们让nums1[n - 1] 与nums2[m - 1]比较大小

大的就放到nums1的末尾:

如图所示:

 在  m 小于0之前,一直循环遍历

 所以·我们实现代码为:

void merge(int *nums1, int n, int* nums2, int m)
{
    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--];
           
        }
        else
        {
            nums1[l3--] = nums2[l2--];
            
        }
    }
   
        while (l2 >= 0)
        {
            nums1[l3--] = nums2[l2--];
        }
    
    
    
}

代码测试:

二、单链表算法

 如果还不了解单链表的同学可以先去我的另一篇博客看看单链表的知识,这对数据结构的学习有很大的帮助!!

链接:http://t.csdnimg.cn/wS03W

1.移除链表元素 

题目链接:https://leetcode.cn/problems/remove-linked-list-elements/description/

这样的题我们能相处什么样的思路呢?

也许我们可以试试这样解决问题: 

再通过比对节点中存储的数据,若不要删除的节点的数据,就直接尾插:

 

否则直接跳过:

 

在最后一定要将newtail->next == NULL,否则新链表依然会包含后面需要被删除的节点!!

接下来,我们来实现一下代码:

 typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode* newhead = NULL;
    ListNode* newtail = NULL;
    newhead = newtail = (ListNode*)malloc(sizeof(ListNode));
    ListNode* pcur = head;
    while (pcur)
    {
        if (pcur->val != val)
        {
            newtail->next = pcur;
            newtail = newtail->next;
        }
        pcur = pcur->next;
    }
    newtail->next = NULL;
    ListNode* ret = newhead->next;
//最后要将哨兵位释放掉, 避免空间的浪费!!!
    free(newhead);
    newhead = NULL;
    return ret;
}

代码测试1:

2.翻转链表

题目链接:https://leetcode.cn/problems/reverse-linked-list/description/
 

这样的题我们能用什么样的思路来写呢?

 这里作者菌,就为大家实现两种思路:
1.头插原链表

什么意思呢?我们画图来理解一下:

这样是不是就十分的清楚了:

接下来我们就可以直接来实现代码了:

 

typedef struct ListNode LN;
LN* reverseList(struct ListNode* head) {
    //思路1:头插原链表
    LN* n1, *n2;
    n1 = n2 = head;
    if (head == NULL)//这里一定要注意处理空指针的情况,也就是实例三
    return NULL;
    LN* pcur = head->next;
    while (pcur)
    {
        LN* temp = pcur->next;
        pcur->next = n1;
        n1 = pcur;
        pcur = temp;
    }
    n2->next = NULL;
    return n1;
}

代码测试:

 接下来,我们再来学习下一个思路:

这个思路我们就直接在原链表的基础上修改指针的指向:

我们先创建三个指针:

n1 n2 n3,他们分别指向不同的位置,如图:

有了大致的思路,我们接下来要解决一些,细节性的问题:

1.三个指针到最后,哪一个指针才能成为成为最后修改后链表的头结点?

2.三指针会出现位移差,所以要注意,不要出现对NULL的解引用操作!!!

接下来我们可以来实现代码了:

 LN* n1, *n2, *n3;
    n1 = NULL;
    n2 = head;
    if (head == NULL)//还是要注意对NULL的处理!!
    return NULL;
    n3 = head->next;
    while (n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if (n3)//n3会最先走到空,为了防止出现对NULL的解引用,我们要添加这一步
        n3 = n3->next;
    }
    return n1;
}

代码测试:

 3.合并两个有序的链表

题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/description/

 这道题和我们上面的合并两个有序数组有异曲同工之妙。

我们还是可以用上面的数字比较,以此比较插入:

有了思路,我们实现代码就不难了:

 

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if (list1 == NULL)//首先处理NULL指针的情况
    return list2;
    if (list2 == NULL)
    return list1;
    if (list1 == NULL && list2 == NULL)
    return NULL;
    ListNode* newhead, *newtail;
    newhead = newtail = (ListNode*)malloc(sizeof(ListNode));
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            newtail->next = list1;
            newtail = newtail->next;
            list1 = list1->next;
        }
        else
        {
            newtail->next = list2;
            newtail = newtail->next;
            list2 = list2->next;
        }
    }
    while (list1)
    {
        newtail->next = list1;
        list1 = list1->next;
        newtail = newtail->next;
    }
     while (list2)
    {
        newtail->next = list2;
        list2 = list2->next;
        newtail = newtail->next;
    }
//注意尾节点的next指针一定要指向NULL
    newtail->next = NULL;
    ListNode* ret = newhead->next;
    //释放哨兵位,以防空间浪费!!
    free(newhead);
    newhead = NULL;
    return ret;
}

代码测试:

 好,今天的学习就到这里,我们下期再见,拜拜!!

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

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

相关文章

Spring源码(六)--BeanFactory 实现与继承关系

BeanFactory 实现与继承关系 这些接口和类的源码&#xff0c; 每一个都可以看一下。 ListableBeanFactory 由bean工厂实现的BeanFactory接口的扩展&#xff0c;这些bean工厂可以枚举它们所有的bean实例&#xff0c;而不是按客户端请求逐个按名称进行bean查找。 Hierarchic…

C# yaml 配置文件的用法(一)

目录 一、简介 二、yaml 的符号 1.冒号 2.短横杆 3.文档分隔符 4.保留换行符 5.注释 6.锚点 7.NULL值 8.合并 一、简介 YAML&#xff08;YAML Aint Markup Language&#xff09;是一种数据序列化标准&#xff0c;广泛用于配置文件、数据交换和存储。YAML的设计目标是…

Kotlin 协程 — 基础

Kotlin 协程 — 基础 协程已经存在一段时间了&#xff0c;关于它的各种文章也很多。但我发现想要了解它还比较费时&#xff0c;所以我花了一段时间才真正理解了协程的基础知识以及它的工作原理。因此&#xff0c;我想分享一些我理解到的内容。 什么是协程&#xff1f; 协程代表…

【MySQL进阶之路 | 高级篇】事务的ACID特性

1. 数据库事务概述 事务是数据库区别于文件系统的重要特性之一&#xff0c;当我们有了事务就会让数据库始终保持一致性&#xff0c;同时我们还能通过事务的机制恢复到某个时间点&#xff0c;这样可以保证给已提交到数据库的修改不会因为系统崩溃而丢失。 1.1 基本概念 事务&…

企业微信获客助手广告平台深度回传/双回传设置教程参考

很多商家在使用【转化宝】进行推广时只采用了单回传&#xff0c;其实很多情况下单回传即可满足推广模型优化需求&#xff1b;但是最近很多专业化广告运营的代投或运营都开始采用双回传&#xff0c;【转化宝】支持抖音巨量引擎、百度营销广告、快手广告、腾讯广告等均支出深度优…

微信小程序开发:项目程序代码构成

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

开源模型应用落地-LangChain高阶-记忆组件-ConversationBufferMemory正确使用(一)

一、前言 LangChain 的记忆组件发挥着至关重要的作用&#xff0c;其旨在协助大语言模型&#xff08;LLM&#xff09;有效地留存历史对话信息。通过这一功能&#xff0c;使得大语言模型在对话过程中能够更出色地维持上下文的连贯性和一致性&#xff0c;进而能够像人类的记忆运作…

【网络安全】构建稳固与安全的网络环境:从“微软蓝屏”事件中汲取的教训

发生什么事了&#xff1f; 近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件&#xff0c;源于美国电脑安全技术公司“众击”提供的一个…

中断和EXIT原理介绍

中断和EXIT原理介绍 一、中断的介绍&#xff1f;二、EXIT的介绍1.EXIT作用2.EXIT的详情3.EXIT中AFIO复用的作用4.STM32中AFIO复用作用 一、中断的介绍&#xff1f; 二、EXIT的介绍 EXTI&#xff08;Extern Interrupt&#xff09;外部中断 1.EXIT作用 EXTI可以监测指定GPIO口…

Java学习笔记(五)数组、冒泡排序

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java数组、冒泡排序使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题可以在评论区留言 …

力扣SQL50 指定日期的产品价格 双重子查询 coalesce

Problem: 1164. 指定日期的产品价格 coalesce 的使用 简洁版 &#x1f468;‍&#x1f3eb; 参考题解 select distinct p1.product_id,coalesce((select p2.new_pricefrom Products p2where p2.product_id p1.product_id and p2.change_date < 2019-08-16order by p2.…

Web前端Promise

Promise介绍与使用 Promise是什么&#xff1f; 1.抽象表达&#xff1a; Promise是一门新的技术&#xff08;ES6规范&#xff09;Promise是JS中进行异步编程的新解决方案备注&#xff1a;旧方案是单纯使用回调函数 2.具体表达&#xff1a; 从语法上来说&#xff1a;Promise…

xmind--如何快速将Excel表中多列数据,复制到XMind分成多级主题

每次要将表格中的数据分成多级时&#xff0c;只能复制粘贴吗 快来试试这个简易的方法吧 这个是原始的表格&#xff0c;分成了4级 步骤&#xff1a; 1、我们可以先按照这个层级设置下空列&#xff08;后买你会用到这个空列&#xff09; 二级不用加、三级前面加一列、四级前面加…

前端:Vue学习 - 购物车项目

前端&#xff1a;Vue学习 - 购物车项目 1. json-server&#xff0c;生成后端接口2. 购物车项目 - 实现效果3. 参考代码 - Vuex 1. json-server&#xff0c;生成后端接口 全局安装json-server&#xff0c;json-server官网为&#xff1a;json-server npm install json-server -…

C++ 设计模式(五)——状态模式

状态模式 序言理解源码 序言 设计模式只是一个抽象的设计模式方法&#xff0c;并不是一个固定使用的搭配&#xff0c;就算是普通switch语句&#xff0c;Map&#xff0c;乃至状态机都是状态模式的其中一种实现方法 状态模式看起来好像和策略模式差不多&#xff0c;主要是其的侧…

JavaScript构造函数小挑战

// 编码挑战 #1 /* 使用构造函数实现一辆汽车。一辆汽车有一个品牌和一个速度属性。speed 属性是汽车当前的速度&#xff0c;单位为 km/h&#xff1b; a. 执行一个 “accelerate ”方法&#xff0c;将汽车的速度提高 10&#xff0c;并将新速度记录到控制台&#xff1b; 3. a.…

若依Vue前后端分离版如何部署(windows)(超详细)

一、项目环境准备 下面是项目所需要准备的环境 Node.js redis 1、Node.js下载 下面进入官网可以下载Node.js — 在任何地方运行 JavaScript (nodejs.org)https://nodejs.org/zh-cn 下载完成安装后&#xff0c;需要配置环境变量&#xff0c;首先复制以下nodejs的安…

商汤提出的BRECQ量化框架是个什么?

商汤提出的BRECQ量化框架是个什么&#xff1f; 引言 近年来&#xff0c;深度学习在多个领域取得了显著进展&#xff0c;但其巨大的计算成本和内存占用问题逐渐凸显。为了压缩和加速已训练好的网络&#xff0c;量化成为了一种重要的技术手段。量化主要分为两类&#xff1a;量化…

DAMA学习笔记(七)-数据集成和互操作

1.引言 数据集成和互操作(DII)描述了数据在不同数据存储、应用程序和组织这三者内部和之间进行移动和整合的相关过程。数据集成是将数据整合成物理的或虚拟的一致格式。数据互操作是多个系统之间进行通信的能力。数据集成和互操作的解决方案提供了大多数组织所依赖的基本数据管…

数据库解析一维和二维简易JSON,

项目还在使用Oracle11&#xff0c;不支持后续官方的json解析方式&#xff0c; 在 前年、去年、今年 接连 遇到json解析问题后&#xff08;其实是公司的轮子效率太慢&#xff0c;太复杂&#xff0c;决定自己造个轮子&#xff0c;看看到底为什么慢&#xff0c;是不是真的很复杂&a…