【编织时空三:探究顺序表与链表的数据之旅】

news2025/1/19 11:23:51

本章重点

  • 链表OJ题

1. 删除链表中等于给定值 val 的所有结点。 OJ链接

思路一:删除头结点时另做考虑(由于头结点没有前一个结点)

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

思路二:添加一个虚拟头结点,删除头结点就不用另做考虑

struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode* cur = head;
    struct ListNode* newhead = NULL;
    struct ListNode* tail = NULL;
    while (cur != NULL)
    {
        if (cur->val == val)
        {
            struct ListNode* del = cur;
            cur = cur->next;
            free(del);
        }
        else
        {
            //尾插
            if (tail == NULL)
            {
                newhead = tail = cur;
            }
            else
            {
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }
    }
    if (tail)//如果最后一个数是要删除的,tail就需要置空
        tail->next = NULL;
    return newhead;
}

2. 反转一个单链表。OJ链接

思路:通过三个指针的操作,每次将当前节点反转并向前移动

struct ListNode* reverseList(struct ListNode* head) 
{	
    assert(head);    
    struct ListNode* n1, * n2, * n3;
	n1 = NULL;
	n2 = head;
	n3 = n2->next;
	while (n2)
	{
		//翻转
		n2->next = n1;
		//交换
		n1 = n2;
		n2 = n3;
		//记录位置
        if(n2 != NULL)
		    n3 = n3->next;
	}
	return n1;
}

思路:头插法

struct ListNode* reverseList(struct ListNode* head) 
{
	struct ListNode* cur = head;
	struct ListNode* newhead = NULL;
	while (cur)
	{
		//保存cur下一个结点的位置
		struct ListNode* next = cur->next;
		//头插
		next = newhead;
		newhead = cur;
		//更新
		cur = next;
	}
	return newhead;
}

3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则 返回第二个中间结点。OJ链接

思路:快慢指针的前进方向相同,且它们步伐的「差」是恒定的,使用两个指针变量,刚开始都位于链表的第 1 个结点,一个永远一次只走 1 步,一个永远一次只走 2 步,一个在前,一个在后,同时走。这样当快指针走完的时候,慢指针就来到了链表的中间位置。

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

4. 输入一个链表,输出该链表中倒数第k个结点。OJ链接

首先让快指针先行k步,然后让快慢指针每次同行一步,直到快指针指向空节点,慢指针就是倒数第K个节点。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k)
{
	struct ListNode* fast = pListHead;
	struct ListNode* slow = pListHead;
	while (k--)//走k步
	{
		//链表没有k步长,那么此时倒数就是空
		if (fast == NULL)
			return NULL;
		fast = fast->next;
	}
	while (fast)
	{
		slow = slow->next;
		fast = fast->next;
	}
	return slow;
}

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。OJ链接

思路一:我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
	if (list1 == NULL)
		return list2;
	if (list2 == NULL)
		return list1;
	struct ListNode* newhead = NULL;
	struct ListNode* tail = NULL;
	while (list1 && list2)
	{
		//小值给到新链表上
		if (list1->val < list2->val)
		{
			if (tail == NULL)
			{
				newhead = tail = list1;
			}
			else
			{
				tail->next = list1;
				tail = tail->next;
			}
			list1 = list1->next;
		}
		else
		{
			if (tail == NULL)
			{
				newhead = tail = list2;
			}
			else
			{
				tail->next = list2;
				tail = tail->next;
			}
			list2 = list2->next;
		}
	}
	if (list1)
		tail->next = list1;
	if (list2)
		tail->next = list2;
	return newhead;
}

思路二:哨兵位法,创建一个带头结点的链表,尾插的时候就不需要判断链表是不是为空的尾插情况,最后再释放哨兵位即可。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
	if (list1 == NULL)
		return list2;
	if (list2 == NULL)
		return list1;
	struct ListNode* newhead = NULL;
	struct ListNode* tail = NULL;
	//哨兵位,方便尾插
	newhead = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
	while (list1 && list2)
	{
		//小值给到新链表上
		if (list1->val < list2->val)
		{
			tail->next = list1;
			tail = tail->next;
			list1 = list1->next;
		}
		else
		{
			tail->next = list2;
			tail = tail->next;
			list2 = list2->next;
		}
	}
	if (list1)
		tail->next = list1;
	if (list2)
		tail->next = list2;
	struct ListNode* del = newhead;
	newhead = newhead->next;
	//释放哨兵位
	free(del);
	return newhead;
}

6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。OJ链接

思路:首先创建四个节点lessHead,greaterHead,lessTail,greaterTail ,遍历整个链表,比x小的尾插到lessHead为哨兵位的那个链表,比x大的尾插到greaterHead为哨兵位的那个链表,再把两个链表连接起来 ,创建一个list节点指向这个链表 ,把greaterTail->next置空,避免成环 ,释放lessHead,greaterHead,返回list 

struct ListNode* partition(struct ListNode* pHead, int x)
{
    struct ListNode* lessHead, * greaterHead;
    lessHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    greaterHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* lessTail = lessHead;
    struct ListNode* greaterTail = greaterHead;
    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仍然链接初始链表的结点,需要置空,否则连环
    greaterTail->next = NULL;
    
    struct ListNode* newhead = lessHead->next;
    free(lessHead);
    free(greaterHead);

    return newhead;
}

7. 链表的回文结构。OJ链接

思路:先找到链表的中间结点,再把中间结点之后的逆序,和之前的链表比较值是否相等。

struct ListNode* middleNode(struct ListNode* head)//找中间结点
{
	struct ListNode* fast = head;
	struct ListNode* slow = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		fast = fast->next->next;
	}
	return slow;
}
struct ListNode* reverseList(struct ListNode* head)//反转链表
{
	struct ListNode* cur = head;
	struct ListNode* newhead = NULL;
	while (cur)
	{
		//保存cur下一个结点的位置
		struct ListNode* next = cur->next;
		//头插
		next = newhead;
		newhead = cur;
		//更新
		cur = next;
	}
	return newhead;
}
bool chkPalindrome(struct ListNode* A) //查看值是否相等
{
	struct ListNode* midnode = middleNode(A);
	struct ListNode* reversemidnode = reverseList(midnode);
	while (A && reversemidnode)
	{
		if(A->val != reversemidnode->val)
		{
			return false;
		}
		A = A->next;
		reversemidnode = reversemidnode->next;
	}
	return true;
}

8. 输入两个链表,找出它们的第一个公共结点。OJ链接

思路:先计算两个链表的长度,再让较长的链表走差距步abs(lenA-LenB)长度,然后再依次比较是否相等。

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) 
{
	struct ListNode* curA = headA, * curB = headB;
	//找尾结点 - 结点总数会少一个
	int lenA = 1;//设置为1
	int lenB = 1;//设置为1
	while (curA->next)
	{
		curA = curA->next;
		lenA++;
	}
	while (curB->next)
	{
		curB = curB->next;
		lenB++;
	}
	//两个链表不相交
	if (curA != curB)
		return NULL;

	//找长链表
	struct ListNode* LongList = headA, * ShortList = headB;
	if (lenA < lenB)
	{
		LongList = headB;
		ShortList = headA;
	}

	//长链表走绝对值(lenA - lenB)步
	int count = abs(lenA - lenB);
	while (count--)
	{
		LongList = LongList->next;
	}

	//同时向后走,相同就停下来
	while (LongList != ShortList)
	{
		LongList = LongList->next;
		ShortList = ShortList->next;
	}
	return ShortList;
}

9. 给定一个链表,判断链表中是否有环。OJ链接

思路:快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行, 如果链表 环则一定会在环中相遇,否则快指针率先走到链表的末尾。

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast = head ,* slow = head;
    while(fast && fast->next)
    {
        fast = fast->next;
        slow = slow->next->next;

        if(fast == slow)
           return true;
    }
    return false;
}

 为什么快指针每次走两步,慢指针走一步可以?

  • 假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚 进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。 此时,两个指针每移动一次,快指针走两次,慢指针走一次,之间的距离就缩小一步,直至最后差距为0,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。

快指针一次走3步,走4步,...n步行吗?

10. 给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL。OJ链接

思路一:一个指针从链表起始位置运行,一个指针从相遇点位置绕环,每次都走一步,两个指针最终会在入口点的位置相遇

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fase->next->next;

        //相遇在环内任意一个位置
        if(slow == fast)
        {
            struct ListNode *meet = slow;
            //两结点关系L = (N-1) * C + C - X;
            while(head != meet)
            {
                head = head->next;
                meet = meet->next;
            }
            return meet;
        }
    }
    return NULL;
}

​

 思路二:两个指针相遇的地方的下一个结点置空,下一个结点位置和链表头指针此时就可以转为两条链表求解公共点的问题。

struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB)//相交点 
{
	struct ListNode* curA = headA, * curB = headB;
	//找尾结点 - 结点总数会少一个
	int lenA = 1;//设置为1
	int lenB = 1;//设置为1
	while (curA->next)
	{
		curA = curA->next;
		lenA++;
	}
	while (curB->next)
	{
		curB = curB->next;
		lenB++;
	}
	//两个链表不相交
	if (curA != curB)
		return NULL;

	//找长链表
	struct ListNode* LongList = headA, * ShortList = headB;
	if (lenA < lenB)
	{
		LongList = headB;
		ShortList = headA;
	}

	//长链表走绝对值(lenA - lenB)步
	int count = abs(lenA - lenB);
	while (count--)
	{
		LongList = LongList->next;
	}

	//同时向后走,相同就停下来
	while (LongList != ShortList)
	{
		LongList = LongList->next;
		ShortList = ShortList->next;
	}
	return ShortList;
}
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *fast = head,*slow = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fase->next->next;

        //相遇在环内任意一个位置
        if(slow == fast)
        {
            struct ListNode *meet = slow;
            struct ListNode *newhead = meet->next;
            meet->next = NULL;
            return getIntersectionNode(head,newhead);
        }
    }
    return NULL;
}

11.给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。 要求返回这个链表的深度拷贝。OJ链接

思路:迭代 + 节点拆分

方法:

struct Node* copyRandomList(struct Node* head) {
	struct Node* cur =  head;
    while(cur)
    {
        struct Node* next = cur->next;
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;

        //插入
        cur->next = copy;
        copy->next = next;

        //往后走
        cur = next;
    }

    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;

        //置 copy random
        if(cur->random == NULL)
            copy->random = NULL;
        else
            copy->random = cur->random->next;
        cur = copy->next;
    }

    cur = head;
    struct Node* copyhead = NULL,*cpoytail = NULL;
    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* next = copy->next;

        //copy结点尾插到新链表
        if(cpoytail == NULL)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copytail->next;
        }

        //恢复原链表
        cur->next = next;
        cur = next;
    }
    return copyhead;
}

 本章结束啦!!!

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

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

相关文章

利用Opencv实现人像迁移

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下如何使用Opencv实现人像迁移&#xff0c;欢迎大家一起参与探讨交流~ 本文目录&#xff1a; 一、实验要求二、实验环境三、实验原理及操作1.照片准备2.图像增强3.实现美颜功能4.背景虚化5.图像二值化处理6.人…

操作系统-笔记-第二章-进程调度

目录 二、第二章——【进程调度】 1、调度的概念 &#xff08;1&#xff09;五状态和七状态&#xff08;就绪挂起、阻塞挂起&#xff09; &#xff08;2&#xff09;三层调度 &#xff08;高级、中级、低级&#xff09; &#xff08;3&#xff09;总结 2、调度的切换 &a…

Vue--》打造个性化医疗服务的医院预约系统(六)

今天开始使用 vue3 + ts 搭建一个医院预约系统的前台页面,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关…

RGOS日常管理操作

RGOS日常管理操作 一、前言二、RGOS平台概述2.1、锐捷设备的常用登陆方式2.2、使用Console登入2.3、Telnet远程管理2.4、SSH远程管理2.5、登陆软件&#xff1a;SecureCRT 三、CLI命令行操作3.1、CLI命令行基础3.2、CLI模式3.3、CLI模式互换3.4、命令行特性3.4.1、分屏显示3.4.2…

(六)、深度学习框架中的算子

1、深度学习框架算子的基本概念 深度学习框架中的算子&#xff08;operator&#xff09;是指用于执行各种数学运算和操作的函数或类。这些算子通常被用来构建神经网络的各个层和组件&#xff0c;实现数据的传递、转换和计算。 算子是深度学习模型的基本组成单元&#xff0c;它们…

07-微信小程序-注册页面-模块化

07-微信小程序-注册页面 文章目录 注册页面使用 Page 构造器注册页面参数Object初始数据案例代码 生命周期回调函数组件事件处理函数setData()案例代码 生命周期模块化 注册页面 对于小程序中的每个页面&#xff0c;都需要在页面对应的 js 文件中进行注册&#xff0c;指定页面…

Ribbon 源码分析

Ribbon 源码分析 Ribbon Debug 分析 断点 LoadBalancerInterceptor LoadBalancerInterceptor 实现了 ClientHttpRequestInterceptor 接口&#xff0c;重写了其中的 intercept 方法&#xff0c;用来拦截请求&#xff1b; 获取原始的 uri 和 服务名&#xff0c;调用 LoadBalanc…

Spring Bean的生命周期总结(包含面试题)

目录 一、Bean的初始化过程 1. 加载Spring Bean 2. 解析Bean的定义 3. Bean属性定义 4. BeanFactoryPostProcessor 扩展接口 5. 实例化Bean对象 6. Aware感知 7. 初始化方法 8. 后置处理 9. destroy 销毁 二、Bean的单例与多例模式 2.1 单例模式&#xff08;Sin…

[国产MCU]-W801开发实例-按键与GPIO输入

按键与GPIO输入 文章目录 按键与GPIO输入1、硬件准备2、软件准备3、驱动实现4、驱动测试在前面的文章中,我们成功点亮了LED,同时也知道W801的GPIO是可软件配置的。在这里,将详细介绍如何通过按键控制LED。 1、硬件准备 W801开发板一块微动开关一个10K电阻一个导线若干1uF电容…

JavaScript简介--语句--变量

目录 JavaScript简介 为什么学习 JavaScript JavaScript与ECMAScript的关系 JavaScript版本 JavaScript语句、标识符 语句 标识符 JavaScript保留关键字 变量 变量的命名规则 数据类型 变量的重新赋值 变量提升 运算符 条件语句 循环语句 JavaScript简介 JavaScri…

【RabbitMQ】RabbitMQ整合SpringBoot案例

文章目录 1、前情提要【RabbitMQ】2、RabbitMQ-SpringBoot案例 -fanout模式2.1 实现架构总览2.2 具体实现2.2.1生产者2.2.1消费者 1、前情提要【RabbitMQ】 【RabbitMQ】消息队列-RabbitMQ篇章 RabbitMQ实现流程 2、RabbitMQ-SpringBoot案例 -fanout模式 2.1 实现架构总览…

时域和频域

时 域 时域即时间域&#xff0c;是指以时间作为自变量&#xff0c;振幅作为因变量&#xff0c;在二维坐标图中即 横轴为时间&#xff0c;纵轴为振幅 下面举出例子&#xff1a; 频率为 2 H z 2Hz 2Hz振幅为 10 V 10V 10V 频 域 同理频域为频率为自变量即横轴 &#xff0c;对于…

01信号和系统

目录 一、信号 1.什么是信号 2.信号的分类 (1)确定信号与随机信号 (2)连续时间信号与离散时间信号 (3)周期信号和非周期信号 (4)功率信号与能量信号 归一化能量的定义 归一化功率的定义 二、系统 1.什么是系统 2.系统的分类 (1)连续时间系统和离散时间系统 (2)线…

【PCL-8】方向包围盒OBB

AABB包围盒&#xff1a;边平行于坐标轴的最小六面体&#xff1b; 方向包围盒OBB&#xff1a;相对于坐标轴方向任意的最小立方体。 最小包围盒计算流程&#xff1a; 1、利用PCA主元分析法获得点云的三个主方向&#xff0c;获取质心&#xff0c;计算协方差&#xff0c;得到协方…

AI聊天机器人原来有这么多作用

AI聊天机器人是一种能够模拟人类对话并利用人工智能技术进行自主学习和适应的计算机程序。它们能够根据用户的输入内容来分析用户的需求&#xff0c;并提供相应的回答和建议。今天looklook就来和大家详细讲一下AI聊天机器人到底有什么作用吧。 AI聊天机器人的作用 1、客户服务…

【springboot】mongoTemplate增删改查操作

目录 一、代码示例1.1 pom依赖1.2 application配置1.3 controller1.4 service 二、截图示例2.1 新增2.2 修改2.3 详情2.4 分页2.5 删除 一、代码示例 1.1 pom依赖 <!-- mongodb --> <dependency><groupId>org.springframework.boot</groupId><art…

2023 年 4 款适用于安卓手机的最佳 PDF 转 Word 转换器

尝试在 Android 上将 PDF 文档转换为 Word 文件&#xff1f;好吧&#xff0c;您可能会发现要让它发挥作用几乎是不可能的&#xff0c;至少在没有任何额外工具的情况下是这样。Web 上有用于此类转换的选项&#xff0c;但本地不一定会发生任何情况&#xff08;可能除了一个应用程…

金融语言模型:FinGPT

项目简介 FinGPT是一个开源的金融语言模型&#xff08;LLMs&#xff09;&#xff0c;由FinNLP项目提供。这个项目让对金融领域的自然语言处理&#xff08;NLP&#xff09;感兴趣的人们有了一个可以自由尝试的平台&#xff0c;并提供了一个与专有模型相比更容易获取的金融数据。…

axios / fetch 实现 stream 流式请求

axios 是一个支持node端和浏览器端的易用、简洁且高效的http库。本文主要介绍 axios 如何实现 stream 流式请求&#xff0c;注意这里需要区分 node 环境和浏览器环境。 一、node端 代码演示&#xff1a; const axios require(axios);axios({method: get,url: http://tiven.c…

intern()的使用和理解

如果不是用双引号声明的String对象&#xff0c;可以使用String提供的intern方法&#xff1a;intern方法会从字符串常量池中查询当前字符串是否存在&#xff0c;若不存在就会将当前字符串放入常量池中。比如&#xff1a;String myinfo new String&#xff08;"I Love CSDN…