news 2026/4/18 14:47:53

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/4/18 9:06:07

使用PyCharm开发Sonic插件:高效调试与代码管理技巧

使用PyCharm开发Sonic插件&#xff1a;高效调试与代码管理技巧 在短视频创作、虚拟主播和在线教育等领域&#xff0c;数字人技术正以前所未有的速度渗透进内容生产流程。过去&#xff0c;制作一个会说话的虚拟形象需要专业的3D建模师、动画师和复杂的渲染管线&#xff0c;成本高…

作者头像 李华
网站建设 2026/4/18 2:24:13

本地运行Sonic需要多少显存?实测RTX 3060即可流畅生成

本地运行Sonic需要多少显存&#xff1f;实测RTX 3060即可流畅生成 在短视频、虚拟主播和AI内容创作爆发的今天&#xff0c;越来越多个人开发者与中小企业开始尝试“数字人”视频生成。但传统方案动辄需要A100显卡、专业动作捕捉设备或长达数小时的训练流程&#xff0c;让人望而…

作者头像 李华
网站建设 2026/4/18 6:25:56

NVIDIA显卡驱动版本要求:确保CUDA兼容Sonic运行环境

NVIDIA显卡驱动版本要求&#xff1a;确保CUDA兼容Sonic运行环境 在虚拟主播、AI客服和短视频生成日益普及的今天&#xff0c;语音驱动数字人面部动画的技术正从实验室快速走向生产环境。其中&#xff0c;由腾讯与浙江大学联合推出的Sonic模型&#xff0c;凭借其轻量高效、口型精…

作者头像 李华
网站建设 2026/4/18 8:10:08

大数据领域数据服务的多模态数据处理

大数据领域数据服务的多模态数据处理:原理、技术与实践 引言:多模态数据处理的崛起与挑战 在当今数字化时代,数据正以前所未有的速度和规模增长。根据IDC的预测,到2025年,全球数据总量将达到175ZB。更值得注意的是,这些数据中超过80%是非结构化或半结构化的多模态数据—…

作者头像 李华
网站建设 2026/4/18 6:26:09

老挝湄公河沿岸村庄用Sonic传播水资源保护意识

老挝湄公河沿岸村庄用Sonic传播水资源保护意识&#xff1a;基于轻量级数字人模型的技术实现解析 在老挝湄公河蜿蜒流淌的村落里&#xff0c;一场无声的技术变革正在悄然发生。村民们围坐在村委会的电视前&#xff0c;看着一位熟悉的长者“开口说话”——讲述着如何保护水源、禁…

作者头像 李华
网站建设 2026/4/18 6:30:04

Sonic视频导出格式只有MP4?暂不支持其他封装格式

Sonic视频导出为何只支持MP4&#xff1f;技术取舍背后的工程智慧 在数字人内容爆发式增长的今天&#xff0c;越来越多创作者开始尝试用AI生成“会说话的虚拟形象”。一张静态人脸照片、一段录音&#xff0c;就能驱动出唇形精准、表情自然的动态视频——这正是Sonic这类轻量级语…

作者头像 李华