数据结构:链表的一些经典的OJ题目

news2024/11/15 8:44:12

文章目录

  • 写在前面
  • 链表OJ调试技巧
  • 移除链表元素
  • 反转链表
  • 链表的中间节点
  • 链表中倒数第K个节点
  • 链表分割问题


写在前面

本篇为本人学习链表的过程中遇到的典型OJ题,于是整理出来分享思路和便于后续重新学习,每个标题均可跳转至对应习题,大多为Leetcode


链表OJ调试技巧

Leetcode中只能看到函数体,不能看到链表的具体情况,因此调试存在困难,自己搭建链表又过于繁琐,这里介绍一种很方便的链表调试技巧

原理如下

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

struct ListNode
{
	int val;
	struct ListNode* next;
};

int main()
{
	struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));
	assert(n1);
	struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));
	assert(n2);
	struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));
	assert(n3);
	struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));
	assert(n4);
	struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));
	assert(n5);

	n1->val = 1;
	n2->val = 2;
	n3->val = 3;
	n4->val = 4;
	n5->val = 5;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = n5;
	n5->next = NULL;
}

我们只需要创建出每一个节点,接着把这些节点都连在一起,手动创建一个链表,链表中需要多少个元素自己搭建,这样可以便于调试,解决OJ中遇到的问题~~

介绍完调试的方法,正式总结链表中的OJ题目


移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

在这里插入图片描述

1.遍历,是val的删掉

一种暴力解决的方法,图示如下

在这里插入图片描述

代码实现如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* prev=NULL;
    struct ListNode* cur=head;
    while(cur)
    {
        if(cur->val==val)
        {
            if(prev)
            {
                prev->next=cur->next;
                free(cur);
                cur=prev->next;
            }
            else
            {
                cur=head->next;
                free(head);
                head=cur;
            }
        }
        else
        {
            prev=cur;
            cur=cur->next;
        }
    }
    return head;
}

2.遍历链表,把不是val的拿下来尾插

图解过程如下:

在这里插入图片描述

代码如下所示

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur=head;
    struct ListNode* newnode = NULL;
    struct ListNode* tail = NULL;
    while(cur)
    {
        if(cur->val!=val)
        {
            if(tail==NULL)
            {
                newnode=tail=cur;
            }
            else
            {
                tail->next=cur;
                tail=tail->next;
            }

            cur=cur->next;
        }
        else
        {
            struct ListNode* del=cur;
            cur=cur->next;
            free(del);
        }
    }

    if(tail)
        tail->next=NULL;

    return newnode;
}

反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

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

在这里插入图片描述

下面介绍两种思路,均能解决问题

1.改变节点指向

在这里插入图片描述
定义三个节点(思考为什么定义三个而不是两个?)而后通过指针遍历把cur指向的节点方向指向前一个,然后通过迭代到末尾,当cur为空退出循环

代码实现如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode* reverseList(struct ListNode* head)
{
    if(head==NULL)
        return NULL;

    struct ListNode* prev=NULL;
    struct ListNode* cur=head;
    struct ListNode* next=head->next;
    
    while(cur)
    {
        cur->next=prev;

        prev=cur;
        cur=next;
        if(next)
            next=next->next;
    }

    return prev;
}

2.将每一个节点都头插

是一个新方法,只需要把新创建一个头,然后把每一个节点都头插进来即可,下面为原理图

在这里插入图片描述

代码实现如下

struct ListNode* reverseList(struct ListNode* head)
{
	struct ListNode* cur = head;
	struct ListNode* rhead = NULL;

	while (cur)
	{
		struct ListNode* next = cur->next;
		
		//头插
		cur->next = rhead;
		rhead = cur;

		//迭代
		cur = next;
	}
	return rhead;
}

关于代码简洁性:

代码简洁程度取决于特殊情况的处理,例如,假设这里把next定义在while循环外,那么假设参数为NULL,就需要对特殊情况做处理,此时代码就多了if条件,整体来说简洁程度就降低了

简洁程度需要对特殊情况的处理足够好,才能足够精炼解决问题


链表的中间节点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

在这里插入图片描述
1.遍历链表求长度

代码实现如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* plist=head;
    int flag=0;
    while(plist)
    {
        while(plist->next!=NULL)
        {
            plist=plist->next;
            flag++;
        }
        break;
    }
    flag+=1;
    flag=flag/2+1;
    for(int i=1;i<flag;i++)
    {
        head=head->next;
    }
    return head;
}

2.快慢指针法

定义两个指针,一个指针是慢指针,每次走一步;一个指针是快指针,每次走两步。当快指针走到末尾时,慢指针走的位置恰好为整个链表的一半

相比起思路1来说,时间复杂度只是O(N),从时间复杂度来讲,这个方法更优

具体实现原理如下图所示

在这里插入图片描述

代码实现如下

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow=head;
    struct ListNode* fast=head;
    while(fast!=NULL && fast->next!=NULL)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

链表中倒数第K个节点

描述
输入一个链表,输出该链表中倒数第k个结点。

输入:1,{1,2,3,4,5}
输出:{5}

在这里插入图片描述

1.遍历求长度,根据k求结果

代码思路和上题十分类似,十分简单

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode* cur=pListHead;
    struct ListNode* prev=pListHead;
    int flag=0;
    while(cur)
    {
        cur=cur->next;
        flag++;
    }
    if(k>flag)
    {
        return NULL;
    }
    for(int i=0;i<flag-k;i++)
    {
        prev=prev->next;
    }
    return prev;
}

2.仿照上题,快慢指针的方法解决

和上题相比,略有不同的一点是,上面的快指针每次走的是一样的,而对于这个题来说,假设K=4,那么就令快指针每次走四格,其余思路和上题类似

总结原理:这里既然要求的是倒数第k个,那么就令fast指针和slow指针之间差k个,然后两个一起走,当fast指针到末尾的时候,slow所在的位置就是fast位置减去它们一开始的距离差,也就是倒数k个的位置

整体思路是十分巧妙的,其实原理和方法1类似,都是找到最后一个元素,然后再找前面倒数的元素,但相比起来使用快慢指针的方法可以让时间复杂度缩短到最少

代码实现如下

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    struct ListNode* fast=pListHead;
    struct ListNode* slow=pListHead;
    for(int i=0;i<k;i++)
    {
        if(fast)
            fast=fast->next;
        else
         return NULL;
    }
    while(fast)
    {
        fast=fast->next;
        slow=slow->next;
    }
    return slow;
}

这里思考
第一次走k步和走k-1步有什么区别?


链表分割问题

题目描述
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针

由于题目描述中提到,不能改变原来的数据顺序,那么也就意味着只能尾插,因为尾插不会破坏数据顺序

于是,我们要做的实际上就是把小于val的单独拿出来,大于等于val的单独拿出来,创建两个节点让拿出来的这两份数据分别进行尾插,最后再实现链表的合并,即可解决这个问题

代码的实现也相对简单,代码实现如下

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        struct ListNode* lesshead,*lesstail,*greaterhead,*greatertail;
        lesshead=lesstail=(struct ListNode*)malloc(sizeof(struct ListNode));
        greaterhead=greatertail=(struct ListNode*)malloc(sizeof(struct ListNode));

        struct ListNode* cur=pHead;
        while(cur)
        {
            if(cur->val<x)
            {
                lesstail->next=cur;
                lesstail=lesstail->next;
            }
            else 
            {
                greatertail->next=cur;
                greatertail=greatertail->next;
            }
            cur=cur->next;
        }

        //链表合并
        lesstail->next=greaterhead->next;
        greatertail->next=NULL;
        pHead=lesshead->next;
        free(lesshead);
        free(greaterhead);
        return pHead;
    }
};

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

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

相关文章

Autosar通信实战系列02-CAN报文发送周期测试脚本开发及周期不准优化

本文框架 前言1. CAN发送报文的周期测试脚本开发2. 发送报文周期不准的可能原因及优化策略2.1 发送报文的控制逻辑2.2 送报文周期不准的可能原因及优化策略前言 在本系列笔者将结合工作中对通信实战部分的应用经验进一步介绍常用,包括但不限于通信各模块的开发教程,代码逻辑…

nvidia显卡设置 让显卡发挥最大的性能

1、打开官网https://www.nvidia.cn/geforce/drivers/ 查看电脑系统位数和显卡(GPU)的版本 产品系列&#xff1a;Notebooks表示笔记本 2、点击【搜索】-【下载】(game表示游戏驱动)-【下载】 3、双击运行exe文件 4、使用邮箱注册账号时注意要设置正常的年龄 PS设置 1、设置PS使用…

2023 7-29

题目1 删除排序链表重复元素 思路和代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *n…

某ERP系统信息泄露登录后台

漏洞描述 某ERP前台泄露了某api接口,恶意攻击者可通过调用该接口,对用户的账号和密码进行非授权访问,在获取到账号和密码后,恶意攻击者可接管后台。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事…

Unity 工具之 NuGetForUnity 包管理器,方便在 Unity 中的进行包管理的简单使用

Unity 工具之 NuGetForUnity 包管理器&#xff0c;方便在 Unity 中的进行包管理的简单使用 目录 Unity 工具之 NuGetForUnity 包管理器&#xff0c;方便在 Unity 中的进行包管理的简单使用 一、简单介绍 二、NuGetForUnity 的下载导入 Unity 三、NuGetForUnity 在 Unity 的…

文件夹加密软件哪个好?文件夹加密软件排行榜

想要加密文件夹&#xff0c;使用专业的文件夹加密软件可以安全有效地加密电脑文件夹。那么&#xff0c;文件夹加密软件哪个好&#xff1f;文件夹加密软件排行榜了解一下。 TOP 1&#xff1a;文件夹加密超级大师 软件亮点&#xff1a;极致安全&#xff0c;5种文件夹加密类型 文…

EMC学习笔记(二十)EMC常用元件简单介绍(二)

EMC常用元件简单介绍&#xff08;二&#xff09; 1.瞬态抑制二极管&#xff08;TVS&#xff09;2.气体放电管3.半导体放电管 电磁兼容性元件是解决电磁干扰发射和电磁敏感度问题的关键,正确选择和使用这些元件是做好电磁兼容性设计的前提。由于每一种电子元件都有它各自的特性,…

谈 Scratch 作品 —— “木筏生存”

资源下载链接&#xff1a; https://download.csdn.net/download/leyang0910/88090187 资源情况&#xff1a; 角色数量&#xff1a;3&#xff0c;素材数量&#xff1a;54&#xff0c;积木数量&#xff1a;6349&#xff0c;音频数量&#xff1a;9 游戏说明&#xff1a; 这是一款…

擎创技术流 | 深入浅出运维可观测工具(二):eBPF应用中常见问题

上期跟大家聊了下eBPF的发展历史还有特性&#xff0c;点击这里↓↓↓擎创技术流 | 深入浅出运维可观测工具&#xff08;一&#xff09;&#xff1a;聊聊eBPF的前世今生&#xff0c;一键回看上期精彩内容。 这期主要跟大家分享下eBPF在应用过程中可能出现的问题&#xff0c;希望…

leetcode 122. 买卖股票的最佳时机 II

2023.7.29 把整体利润拆分成每天的利润&#xff0c;将股票值想象成一个折线图&#xff0c;将所有上升的值相加即可。 代码&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {int ans 0;for(int i1; i<prices.size(); i){if(prices[i]-…

神经网络简单介绍

人工神经网络(artififial neural network) 简称神经网络&#xff0c;它是一种模仿生物神经网络结构和功能的非线性数学模型。 神经网络通过输入层接受原始特征信息&#xff0c;再通过隐藏层进行特征信息的加工和提取&#xff0c;最后通过输出层输出结果。 根据需要神经网络可以…

树及其遍历

文章目录 树树定义专业术语树分类 二叉树分类存储连续存储&#xff08;完全二叉树&#xff09;链式存储一般树的存储森林的存储 线索二叉树哈夫曼树构造步骤 遍历先序遍历中序遍历后续遍历 链式二叉树遍历具体代码已知两种遍历序列求原始二叉树已知先序和中序求后序已知中序和后…

mtk桌面壁纸滚动

路径&#xff1a;packages\apps\Launcher3\src\com\android\launcher3 目前代码中是未打开的状态 packages\apps\Launcher3\src\com\android\launcher3\util\WallpaperOffsetInterpolator.java 涉及到的方法有&#xff1a;&#xff08;重要性从上往下&#xff09; updateOffs…

架构的分类

目录 一、 RUP41 架构 1.1 RUP41架构方法概述 1.2 RUP41架构总体 1.3 RUP41架构方法内容 1.3.1 逻辑视图 1.3.2 开发视图 1.3.3 物理视图 1.3.4 处理视图 1.3.5 场景视图 ​二、 TOGAF9 架构 2.1 TOGAF9 架构概述 2.2 TOGAF9 架构分类 2.2.1 业务架构 2.2.2 数据架…

Vue2 第四节 计算属性,监视属性

1.计算属性 2.监视属性 3.计算属性与监视属性之间的关系 一.计算属性 定义&#xff1a;要用的属性不存在&#xff0c;要通过已有属性计算得来原理&#xff1a;底层借助了Object.defineproperty方法提供的getter和setterget函数什么时候会执行&#xff1a;初次读取的时候会执…

银河麒麟安装solr及配置文件修改

下载并解压 将从官网下载的solr.tar.gz解压到目标文件夹 解压命令&#xff1a;tar -zxvf solr-xxx.tar.gz 修改配置文件 进入目录&#xff1a; /home/solr/solr-8.5.1/bin/init.d 修改solr文件里面部分配置 修改solr服务路径 SOLR_INSTALL_DIR"/opt/solr" 改为…

app稳定性测试-iOS篇

稳定性测试&#xff1a;测试应用程序在长时间运行过程中是否存在内存泄漏、崩溃等问题&#xff0c;以确保应用程序具有较高的稳定性和可靠性。 对于安卓端&#xff0c;官方提供了很好的稳定性测试工具&#xff1a;monkey。 相比较而言&#xff0c;iOS则没有&#xff0c;而且当前…

#systemverilog# 说说Systemverilog中《automatic》那些事儿(关于for循环内置变量研究)

#systemverilog# 说说Systemverilog中《static》那些事儿(理论篇1) 上面的截图,是来自于前面的文章,当时留下的疑问,今天和大家是实践一下小细节问题。 一 for 内置声明变量修饰 1.1 static 修饰

第八章浮点单元(Cortex-M7 Processor)

目录 浮点单元 8.1关于FPU 8.2FPU功能描述 8.2.1操作方式 8.2.2符合IEEE 754标准 8.2.3异常 8.3FPU编程器模型 浮点单元 介绍FPU (Floating Point Unit)。 它包含以下部分: 关于FPU的信息见8-2。FPU功能描述见8-3。FPU程序员模型在第8-5页。 8.1关于FPU Cortex-M7处…

基于RK3588+FPGA+AI算法定制的智慧交通与智能安防解决方案

随着物联网、大数据、人工智能等技术的快速发展&#xff0c;边缘计算已成为当前信息技术领域的一个热门话题。在物联网领域&#xff0c;边缘计算被广泛应用于智慧交通、智能安防、工业等多个领域。因此&#xff0c;基于边缘计算技术的工业主板设计方案也受到越来越多人的关注。…