算法闯关日记 Episode :解锁链表新副本——破解「相交」迷局与「回文」谜题

算法闯关日记 Episode :解锁链表新副本——破解「相交」迷局与「回文」谜题


在这里插入图片描述

🔥@晨非辰Tong: 个人主页
👀专栏:《C语言》《数据结构与算法入门指南》
💪学习阶段:C语言、数据结构与算法初学者
⏳“人理解迭代,神理解递归。”


文章目录


引言

在算法学习中,链表因其灵活的结构成为高频考点。本期将攻克两大经典问题:「相交链表」 与「链表的回文结构」。跟随本篇题解,逐步拆解问题,提升链表类问题的实战能力

一、相交链表

题目链接:160.相交链表-力扣(LeetCode)
  • 题目描述:

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:

在这里插入图片描述


题目数据保证整个链式结构中不存在环。注意,函数返回结果后,链表必须保持其原始结构 。

  • 实现示例:
在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

1.1 思路解答 + 作图演示

  • 算法思路:
    由于是单链表,节点只保存着下一个节点的地址,那么初步可以确定为遍历两个链表,那么该如何对俩个链表进行遍历呢?
  1. 简单的对两个链表分别进行遍历,寻找相同的next(相同的节点地址)。(废弃)
在这里插入图片描述


经过作图发现,只是简单的分别遍历比较的话,如果两个链表的长度不相同,就会导致在相交的节点两个链表的遍历会错开,直到遍历完都没有找到。那么,何解??

  1. 先让较长的链表走完两个链表长度的差值。(最优解)

以较长的链表B为基准,将链表A依次与链表B对比,寻找相同的节点。(可行,备选)

在这里插入图片描述


这个方法可行,但是显然易见:需要两个循环的嵌套,时间复杂度O(N^2^)

在这里插入图片描述


那么,为了解决来链表长度不同导致的遍历步调不一致,就让长的链表先走,让两个来年表同起点。经过作图发现,让B链表先走差值(1位),之后二者的遍历步调一致,最终在节点c1相遇。时间复杂度:O(N)

1.2 验证算法

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */typedefstructListNode ListNode;structListNode*getIntersectionNode(structListNode*headA,structListNode*headB){//求出两个链表的长度//创建临时变量,不改变链表结构 ListNode* pa = headA; ListNode* pb = headB;int sizeA =0, sizeB =0;//循环求长度while(pa){ sizeA++; pa = pa->next;}while(pb){ sizeB++; pb = pb->next;}//长度差值,使用绝对值函数int gap =abs(sizeA - sizeB);//判断链表的长短 ListNode* longlist = headA; ListNode* shortlist = headB;if(sizeA < sizeB){ longlist = headB; shortlist = headA;}//长链表先走gap步while(gap--){ longlist = longlist->next;}//同时遍历while(longlist){//节点相同if(longlist == shortlist){return longlist;}//节点不同 longlist = longlist->next; shortlist = shortlist->next;}//没有相同的节点returnNULL;}
在这里插入图片描述

二、链表的回文结构

题目链接:链表的回文结构_牛客网
  • 题目描述:
    对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
    给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900

实现示例:

在这里插入图片描述

2.1 思路解答 + 作图演示

  • 算法思路:
  1. 找到链表的中间节点,然后将后面的链表进行反转,再将原链表的前半部分和反转后的链表进行数值对比。
    这个方法,恰巧前面的练习中寻找中间节点、链表反转已经学过,参考下面两篇博客:反转链表寻找中间节点

因为有链表长度<=900的前提,那么就可以将链表转换为数组(数组大小已知,空间复杂度不变),创建数组将链表节点的数值全部存放,再比较。

在这里插入图片描述


这个思路跟容易就想到了,当然,这是一个取巧的方法,如果题目没有给出关于链表长度的限制,就不能使用这个方法了。

创建新的链表,将原链表的所有节点重新拷贝一份,再将链表进行反转,与原链表比较。

在这里插入图片描述
在这里插入图片描述

2.2 验证算法

  1. 验证算法思路2:将链表转换为数组,创建数组将链表节点的数值全部存放,再比较。
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) {} };*/classPalindromeList{public:boolchkPalindrome(ListNode* A){// write code here//创建数组int arr[900]={0};//遍历链表,将数值存放在数组中 ListNode* pcur = A;int index =0;while(pcur){ arr[index++]= pcur->val; pcur = pcur->next;}//创建左右指针,双方向遍历数组比较int left =0;int right = index -1;while(left < right){if(arr[left]!= arr[right]){returnfalse;} left++; right--;}returntrue;}};
在这里插入图片描述
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) {} };*/classPalindromeList{public://找到中间节点 ListNode*middleNode(structListNode* head){//创建快慢指针 ListNode* fast,*slow;//首先都指向头节点 fast = head; slow = head;//循环条件将两种情况都包含while(fast !=NULL&& fast->next !=NULL)//条件换顺序?{//慢指针移动一节点 slow = slow->next;//快指针移动两个节点 fast = fast->next->next;}//返回slowreturn slow;}//从中间节点开始反转链表 ListNode*reverseList(structListNode* head){//创建三个指针 ListNode* n1,* n2,* n3;//链表为空if(head ==NULL){return head;}//链表不为空//首先n1指向空 n1 =NULL; n2 = head; n3 = n2->next;while(n2)//循环条件n2不为空(不超出链表){ n2->next = n1; n1 = n2; n2 = n3;//当n2到达尾节点时,n3为空,不能对空指针解引用if(n3){ n3 = n3->next;}}return n1;}boolchkPalindrome(ListNode* A){//找到中间节点 ListNode* mid =middleNode(A);//从中间节点开始反转 ListNode* right =reverseList(mid);//遍历原链表和反转之后的链表比较值是否相等 ListNode* left = A;while(right){if(right->val != left->val){returnfalse;} right = right->next; left = left->next;}returntrue;}};

总结

🍓 我是晨非辰Tong!若这篇技术干货帮你打通了学习中的卡点: 👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用 💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑 🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解 技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标! 

结语:

「相交链表」的关键在于同步遍历:通过计算长度差与双指针同步移动,巧妙化解链表长度不一致的遍历难题,最终实现O(N)时间复杂度的高效判定。
「链表的回文结构」则需多技巧组合:寻找中间节点(快慢指针)与局部反转(三指针法)的结合,既满足了O(1)空间复杂度的要求,又通过对称比较精准判断回文特性。
两题共同体现了链表问题的核心解法——通过指针操作优化遍历路径,将复杂问题拆解为已知子问题,最终用简洁逻辑攻克难关。

Read more

【C++指南】STL容器的安全革命:如何封装Vector杜绝越界访问与迭代器失效?

【C++指南】STL容器的安全革命:如何封装Vector杜绝越界访问与迭代器失效?

🌟 各位看官好,我是egoist2023! 🌍 种一棵树最好是十年前,其次是现在! 🚀 使用STL的三个境界:能用,明理,能扩展 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦! 了解vector常用接口 vector是C++标准模板库中的部分内容,中文偶尔译作“容器”,但并不准确。它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。 常见构造 (constructor)构造函数声明接口说明vector()(重点)无参构造vector(size_type n, const value_type& val = value_type())构造并初始化n个valvector (const vector& x); (重点)拷贝构造vector (InputIterator first, InputIterator last)

By Ne0inhk
C++11新特性(下)----《Hello C++ Wrold!》(26)--(C/C++)

C++11新特性(下)----《Hello C++ Wrold!》(26)--(C/C++)

文章目录 * 前言 * lambda表达式 * 可变参数模板 * 展开参数包的方法 * 应用 * 包装器 * fiction包装器 * bind函数 * 作业部分 前言 在 C++11 标准带来的诸多革命性特性中,“简化代码编写” 与 “统一可调用对象管理” 是两大核心目标。lambda 表达式解决了传统仿函数 “定义繁琐、复用性低” 的痛点,让局部场景下的自定义逻辑(如排序规则、回调函数)能以更简洁的匿名函数形式实现;可变参数模板则打破了模板参数数量固定的限制,为 STL 容器(如emplace_back)和通用函数设计提供了灵活的参数处理能力;而 function 包装器与 bind 函数,则进一步整合了函数指针、仿函数、lambda 等不同类型的可调用对象,实现了统一管理与参数适配,甚至让可调用对象存储到容器中成为可能。 这些特性并非孤立存在 ——lambda 的底层依赖仿函数实现,可变参数模板为emplace系列接口提供了技术支撑,

By Ne0inhk
平衡二叉搜索树之 红黑 树的模拟实现【C++】

平衡二叉搜索树之 红黑 树的模拟实现【C++】

文章目录 * 红黑树的简单介绍 * 定义 * 红黑树的特性 * 红黑树的应用 * 全部的实现代码放在了文章末尾 * 准备工作 * 包含头文件 * 类的成员变量和红黑树节点的定义 * 构造函数和拷贝构造 * swap和赋值运算符重载 * 析构函数 * find * insert【重要】 * 第一步:按照二叉搜索树的方式插入新节点 * 第二步:调整颜色,维护红黑树的规则 * 情况一:新插入的节点的父亲节点颜色为黑 * 情况二:新插入的节点的父亲节点颜色为红,且叔叔节点不为空且为红 * 情况三:新插入的节点的父亲节点颜色为红,且叔叔节点为空或者为黑 * empty * size * 中序遍历 * 红黑树和AVL树的比较 * 全部代码 红黑树的简单介绍 定义 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,只能是Red或Black。 通过对任何一条从根到空节点的路径上各个结点着色方式的限制 红黑树确保没有一条路径会比其他路径长出俩倍,即最长路径的长度最多是最短

By Ne0inhk

C++:实现四舍五入(附带源码)

项目背景详细介绍 在数学计算、金融系统、工程测量、图像处理以及各种业务系统中,四舍五入是最基础、也是最容易被低估的一个问题。 很多初学者认为“四舍五入”只是简单地调用一个函数即可,例如: round(x) 但在实际开发中,问题远比想象复杂: * 不同业务对“四舍五入”的定义并不完全相同 * C++ 标准库中的 round / floor / ceil 行为容易混淆 * 浮点数本身存在精度误差 * 保留 N 位小数时,错误极易产生 例如: 2.675 四舍五入到 2 位小数 结果是 2.67 还是 2.68? 在不同语言、不同实现中,答案甚至可能不同。 因此,深入理解并亲自实现“四舍五入”逻辑,是 C+

By Ne0inhk