news 2026/1/16 5:52:27

Leetcode 93 找出临界点之间的最小和最大距离

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Leetcode 93 找出临界点之间的最小和最大距离

1 题目

2058. 找出临界点之间的最小和最大距离

链表中的临界点定义为一个局部极大值点局部极小值点 。

如果当前节点的值严格大于前一个节点和后一个节点,那么这个节点就是一个局部极大值点

如果当前节点的值严格小于前一个节点和后一个节点,那么这个节点就是一个局部极小值点

注意:节点只有在同时存在前一个节点和后一个节点的情况下,才能成为一个局部极大值点 / 极小值点

给你一个链表head,返回一个长度为 2 的数组[minDistance, maxDistance],其中minDistance是任意两个不同临界点之间的最小距离,maxDistance是任意两个不同临界点之间的最大距离。如果临界点少于两个,则返回[-1,-1]

示例 1:

输入:head = [3,1]输出:[-1,-1]解释:链表 [3,1] 中不存在临界点。

示例 2:

输入:head = [5,3,1,2,5,1,2]输出:[1,3]解释:存在三个临界点: - [5,3,1,2,5,1,2]:第三个节点是一个局部极小值点,因为 1 比 3 和 2 小。 - [5,3,1,2,5,1,2]:第五个节点是一个局部极大值点,因为 5 比 2 和 1 大。 - [5,3,1,2,5,1,2]:第六个节点是一个局部极小值点,因为 1 比 5 和 2 小。 第五个节点和第六个节点之间距离最小。minDistance = 6 - 5 = 1 。 第三个节点和第六个节点之间距离最大。maxDistance = 6 - 3 = 3 。

示例 3:

输入:head = [1,3,2,2,3,2,2,2,7]输出:[3,3]解释:存在两个临界点: - [1,3,2,2,3,2,2,2,7]:第二个节点是一个局部极大值点,因为 3 比 1 和 2 大。 - [1,3,2,2,3,2,2,2,7]:第五个节点是一个局部极大值点,因为 3 比 2 和 2 大。 最小和最大距离都存在于第二个节点和第五个节点之间。 因此,minDistance 和 maxDistance 是 5 - 2 = 3 。 注意,最后一个节点不算一个局部极大值点,因为它之后就没有节点了。

示例 4:

输入:head = [2,3,3,2]输出:[-1,-1]解释:链表 [2,3,3,2] 中不存在临界点。

2 代码实现

思考

高中导数题目,用链表实现,我看了一下这个题单在链表的遍历里面,最大距离就是第一个临界点和最后的一个临界点之间的距离。最小的距离就是最近的两个临界点距离。

现在要实现的代码有以下难点:

1.如何判断是临界点?

2.怎么存储临界点的“距离”?用数组还是unordered_map?或者用一个count作为计数器?

3.最大的距离或者最小的距离怎么更新?

一点代码都不知道怎么写,只知道循环条件是while ( -> next ! = nullptr)。

这个情况还是先看题解吧(cpp实现)

代码

/** * 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 *next) : val(x), next(next) {} * }; */ class Solution { public: vector<int> nodesBetweenCriticalPoints(ListNode* head) { // 存储所有临界点的位置(从1开始计数) vector<int> criticalPositions; // 边界处理:链表长度小于3时,直接返回[-1,-1] if (head == nullptr || head->next == nullptr || head->next->next == nullptr) { return {-1, -1}; } ListNode* prev = head; // 前一个节点 ListNode* curr = head->next; // 当前节点(从第二个节点开始) int pos = 2; // 当前节点的位置(第二个节点是位置2) // 遍历条件:当前节点必须有后一个节点才能判断临界点 while (curr != nullptr && curr->next != nullptr) { ListNode* nextNode = curr->next; // 判断是否是局部极大值或极小值 bool isMax = (curr->val > prev->val) && (curr->val > nextNode->val); bool isMin = (curr->val < prev->val) && (curr->val < nextNode->val); // 是临界点则记录位置 if (isMax || isMin) { criticalPositions.push_back(pos); } // 指针后移,位置计数+1 prev = curr; curr = nextNode; pos++; } // 临界点数量不足2个,返回[-1,-1] if (criticalPositions.size() < 2) { return {-1, -1}; } // 计算最大距离:最后一个临界点 - 第一个临界点 int maxDist = criticalPositions.back() - criticalPositions[0]; // 计算最小距离:遍历相邻临界点的差值,取最小值 int minDist = INT_MAX; for (int i = 1; i < criticalPositions.size(); i++) { minDist = min(minDist, criticalPositions[i] - criticalPositions[i-1]); } return {minDist, maxDist}; } };

3 题解

一、题目理解与核心思路

你一开始提到这道题像高中导数的极值问题,且明确了两个关键结论:

  • 最大距离 = 第一个临界点位置 - 最后一个临界点位置;
  • 最小距离 = 相邻临界点之间的最小距离;这两个结论是解题的核心,我们的代码也会围绕这个核心展开。

题目核心要求

  1. 临界点定义:仅存在 “前一个节点” 和 “后一个节点” 的中间节点,且满足 “局部极大值(当前值 > 前后)” 或 “局部极小值(当前值 < 前后)”;
  2. 输出要求:若临界点数量 < 2,返回[-1,-1];否则返回[最小距离, 最大距离]

解题关键问题

你的疑问解决方案
如何判断临界点?遍历链表时,对每个中间节点(有前驱 + 后继),判断是否满足 “局部极大 / 极小值” 条件
如何存储临界点?用数组(vector)存储临界点的位置索引(从 1 开始计数),无需复杂结构(如 map)
如何更新最小 / 最大距离?最大距离:直接取数组首尾元素差值;最小距离:遍历数组计算相邻元素差值的最小值

二、完整解题步骤

步骤 1:边界预处理

链表长度 < 3 时(即head/head->next/head->next->next为空),不可能存在临界点,直接返回[-1,-1]

步骤 2:遍历链表,识别并记录临界点

  • 初始化指针:prev(前驱节点,初始为head)、curr(当前节点,初始为head->next);
  • 初始化位置计数器:pos=2curr是第二个节点,位置从 1 开始);
  • 遍历条件:curr != nullptr && curr->next != nullptr(保证当前节点有后继);
  • 临界点判断:
    bool isMax = (curr->val > prev->val) && (curr->val > nextNode->val); bool isMin = (curr->val < prev->val) && (curr->val < nextNode->val);
  • 满足条件则将pos存入criticalPositions数组。

步骤 3:计算最小 / 最大距离

  • 若临界点数量 < 2,返回[-1,-1]
  • 最大距离:criticalPositions.back() - criticalPositions[0](首尾差值);
  • 最小距离:遍历数组,计算相邻元素差值的最小值(初始设为INT_MAX,确保第一次差值能覆盖)。

三、代码逐行解析(结合你的代码框架)

/** * 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 *next) : val(x), next(next) {} * }; */ #include <vector> #include <climits> // 提供INT_MAX常量 using namespace std; class Solution { public: vector<int> nodesBetweenCriticalPoints(ListNode* head) { // 存储临界点的位置(从1开始计数),对应你问的“怎么存储临界点” vector<int> criticalPositions; // 边界处理:链表长度<3,无临界点 if (head == nullptr || head->next == nullptr || head->next->next == nullptr) { return {-1, -1}; } // 初始化指针:prev=前驱节点,curr=当前节点(从第二个节点开始) ListNode* prev = head; ListNode* curr = head->next; int pos = 2; // 当前节点的位置(第二个节点是位置2) // 遍历条件:你提到的while循环,补充了curr非空的安全判断 while (curr != nullptr && curr->next != nullptr) { ListNode* nextNode = curr->next; // 后继节点 // 核心:判断临界点(对应你问的“如何判断临界点”) bool isMax = (curr->val > prev->val) && (curr->val > nextNode->val); bool isMin = (curr->val < prev->val) && (curr->val < nextNode->val); // 是临界点则记录位置 if (isMax || isMin) { criticalPositions.push_back(pos); } // 指针后移,位置计数+1 prev = curr; curr = nextNode; pos++; } // 临界点数量不足2个,返回[-1,-1] if (criticalPositions.size() < 2) { return {-1, -1}; } // 计算最大距离:你总结的“第一个和最后一个临界点的距离” int maxDist = criticalPositions.back() - criticalPositions[0]; // 计算最小距离:你总结的“最近的两个临界点距离” int minDist = INT_MAX; // 初始化为极大值 for (int i = 1; i < criticalPositions.size(); i++) { minDist = min(minDist, criticalPositions[i] - criticalPositions[i-1]); } return {minDist, maxDist}; } };

四、测试用例验证

示例 2:输入[5,3,1,2,5,1,2]

  • 临界点位置:3(1<3 且 1<2)、5(5>2 且 5>1)、6(1<5 且 1<2);
  • 最大距离:6-3=3;
  • 最小距离:6-5=1;
  • 输出:[1,3],符合预期。

示例 3:输入[1,3,2,2,3,2,2,2,7]

  • 临界点位置:2(3>1 且 3<2?不,3>1 且 3>2,是极大值)、5(3>2 且 3>2,是极大值);
  • 最大 / 最小距离:5-2=3;
  • 输出:[3,3],符合预期。

4 小结

  1. 临界点判断:仅中间节点(有前驱 + 后继)能成为临界点,需满足 “严格大于 / 小于前后节点”;
  2. 存储策略:用数组存储临界点位置(从 1 计数),是最简单高效的方式;
  3. 距离计算:最大距离 = 首尾临界点位置差,最小距离 = 相邻临界点位置差的最小值;
  4. 边界处理:链表长度 < 3、临界点数量 < 2 时,均返回[-1,-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 *next) : val(x), next(next) {} * }; */ class Solution { public: vector<int> nodesBetweenCriticalPoints(ListNode* head) { vector<int> criticalPos ; if(head == nullptr || head -> next == nullptr || head -> next -> next == nullptr){ return {-1 , -1 }; } ListNode* prev = head ; ListNode* cur = head -> next ; int pos = 2 ; while (cur != nullptr && cur -> next != nullptr){ bool isMax = (cur -> val > prev -> val) &&(cur -> val > cur -> next -> val); bool isMin = (cur -> val < prev -> val) && (cur -> val < cur -> next -> val); if (isMax || isMin){ criticalPos.push_back(pos); } prev = cur ; cur = cur -> next ; pos++; } if(criticalPos.size() < 2){ return{ -1 , -1 }; } int maxDistance = criticalPos.back() - criticalPos[0]; int minDistance = INT_MAX; for (int i = 1 ; i < criticalPos.size() ; i++){ minDistance = min (criticalPos[i] - criticalPos[i - 1],minDistance); } return{minDistance,maxDistance}; } };

手写错误小结

我自己写有几个错误,但是我纠正过来了,下面是我写的错误。

1.循环逻辑放的位置不对,

if(criticalPos.size() < 2){

return{ -1 , -1 };

}

我把这段错误的嵌套在放入数组的循环里面了

2.逻辑表达Boolean没写清楚

bool isMax = (cur -> val > prev -> val) &&(cur -> val > cur -> next -> val);

我一开始没有加这两个小括号

3. 数组的末尾函数乱写,我

int maxDistance = criticalPos.back() - criticalPos[0];

我自己写的是.end(),其实是.back()

criticalPos.back()写成criticalPos.end(),导致编译错误 / 逻辑错误。

本质原因

对 STL 容器的常用函数记忆模糊:

函数返回值用途
.end()迭代器(指向容器末尾的下一个位置)遍历结束标记(如 for 循环)
.back()容器最后一个元素的引用获取最后一个元素的值
.front()容器第一个元素的引用获取第一个元素的值
避坑方法

记核心函数 + 手写小例子

// 随手写测试,强化记忆 vector<int> vec = {3,5,6}; cout << vec.front(); // 输出3(第一个元素) cout << vec.back(); // 输出6(最后一个元素) // cout << vec.end(); // 错误:end()是迭代器,不能直接输出值

写代码时,只要是 “取容器首尾元素的值”,就用.front()/.back();只要是 “遍历的结束位置”,才用.end()

解决 “看得懂写不来” 的核心方法

你现在的问题不是 “思路不懂”,而是 “动手少、细节不熟练”,按以下步骤练,1-2 周就能明显改善:

步骤 1:“拆解式仿写”(从抄到写的过渡)

拿到一道题,先不直接写完整代码,而是:

  1. 把解题步骤拆成 5-8 个小任务(比如 “初始化指针”“判断临界点”“计算最大距离”);
  2. 先写第一个小任务,运行确认没错;
  3. 再写第二个,以此类推。比如这道题,你可以先只写 “遍历链表 + 判断临界点”,打印出临界点位置,确认正确后,再加 “计算距离” 的逻辑。

步骤 2:“刻意记细节”(针对你犯的语法错误)

把你容易错的点整理成 “错题本”,比如:

  • vector 常用函数:back()/front()/size()/push_back()
  • 逻辑表达式必须加括号;
  • 条件判断的位置(比如数量判断要在收集完数据后);每天花 5 分钟看一遍,写代码时刻意提醒自己。

步骤 3:“无参考重写”(关键步骤)

看懂题解后,关闭题解,自己从头写一遍:

  • 第一遍错了没关系,记下来错在哪;
  • 隔 1 小时再写第二遍,直到能一次写对。这道题你已经做到了 “自己写 + 纠正错误”,这是最有价值的一步 —— 错误比正确更重要,因为你找到了自己的薄弱点。
  1. 逻辑结构:判断临界点数量的代码必须放在遍历链表之后,先 “收集数据” 再 “判断处理”;
  2. 语法细节:复合布尔表达式加括号,vector 取首尾元素用back()/front()(而非end());
  3. 练习方法:解决 “看得懂写不来” 的核心是 “拆解任务 + 刻意记细节 + 无参考重写”,而非 “看更多题解”。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/14 14:25:36

Proteus模拟电路仿真元器件应用实战案例

用Proteus打通模拟电路设计的“任督二脉”&#xff1a;从元器件建模到系统级仿真实战你有没有遇到过这样的场景&#xff1f;辛辛苦苦画完PCB&#xff0c;焊好板子&#xff0c;通电一试——信号失真、运放饱和、ADC读数跳变……问题出在哪&#xff1f;是电阻选错了&#xff1f;电…

作者头像 李华
网站建设 2026/1/8 1:26:41

Git Commit规范建议:为Sonic项目贡献代码时的标准格式

Git Commit规范建议&#xff1a;为Sonic项目贡献代码时的标准格式 在开源协作日益复杂的今天&#xff0c;一次看似简单的 git commit 操作&#xff0c;其实承载着远超“保存更改”的意义。尤其是在像 Sonic 这样融合了深度学习模型、可视化工作流与多模块协同的AI生成系统中&a…

作者头像 李华
网站建设 2026/1/7 13:04:33

基里巴斯环礁居民用Sonic记录潮汐变迁日记

基里巴斯环礁居民用Sonic记录潮汐变迁日记&#xff1a;轻量级数字人语音同步技术解析 在太平洋深处的基里巴斯环礁上&#xff0c;老渔民Teuea正对着手机讲述今年潮水来得比往年早了整整两周。他说话时神情凝重——这不是简单的天气变化&#xff0c;而是家园正在被海水一点点吞噬…

作者头像 李华
网站建设 2026/1/12 7:28:10

结合Multisim主数据库开展探究性实验教学:实践案例

用真实器件模型点燃电路探究&#xff1a;Multisim主数据库如何重塑电子实验教学你有没有遇到过这样的学生&#xff1f;他们能准确背出运放的“虚短”“虚断”&#xff0c;也能列出负反馈增益公式&#xff0c;可一旦面对一块实际芯片的数据手册&#xff0c;就两眼发懵&#xff1…

作者头像 李华
网站建设 2026/1/7 20:50:12

JLink驱动下载及设备管理器配置手把手教程

J-Link驱动安装踩坑实录&#xff1a;从“未知设备”到秒连的全流程实战指南 你有没有遇到过这种场景&#xff1f; 新项目刚开板&#xff0c;兴冲冲插上J-Link准备烧录程序&#xff0c;结果Keil弹窗&#xff1a;“Cannot connect to J-Link”。 打开设备管理器一看—— “Un…

作者头像 李华
网站建设 2026/1/9 16:03:07

AI浪潮下的HR生存战:淘汰还是升级,关键看这一步

AI浪潮下的HR生存战&#xff1a;淘汰还是升级&#xff0c;关键看这一步当AI智能体从冰冷工具进化为能独立思考、自主执行的“数字员工”&#xff0c;人力资源领域的无声革命已然来临。事务型、经验型、非数据驱动的HR正被时代浪潮推向边缘&#xff0c;依赖人工筛选、主观判断与…

作者头像 李华