【算法】1019.链表中的下一个更大节点--通俗讲解
一、题目是啥?一句话说清
给定一个链表,对于每个节点,找到它后面第一个值比它大的节点,如果没有则返回0。
示例:
- 输入:2 → 7 → 4 → 3 → 5
- 输出:[7, 0, 5, 5, 0]
二、解题核心
使用单调栈:遍历链表,维护一个单调递减的栈,当遇到比栈顶元素大的节点时,说明找到了栈顶元素的下一个更大节点。
这就像排队时,每个人都在找前面第一个比自己高的人,如果找到了就记住他的身高,找不到就记0。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 单调栈的使用
- 是什么:维护一个栈,栈中存储的是节点的索引和值,保持栈中元素的值单调递减。
- 为什么重要:单调栈可以高效地找到每个元素的下一个更大元素,时间复杂度为O(n)。
2. 栈中存储索引
- 是什么:在栈中存储节点的索引(位置),而不是节点本身。
- 为什么重要:这样我们可以知道当前节点对应结果数组中的哪个位置,从而正确设置结果值。
3. 逆序处理
- 是什么:将链表转换为数组后,从后往前处理,或者使用栈来模拟逆序处理。
- 为什么重要:因为要找的是"下一个"更大节点,从后往前处理可以确保我们处理当前节点时,已经知道了后面所有节点的信息。
四、看图理解流程(通俗理解版本)
假设链表为:2 → 7 → 4 → 3 → 5
- 转换为数组:[2, 7, 4, 3, 5]
- 初始化:结果数组ans = [0, 0, 0, 0, 0],栈stack为空
- 从后往前处理:
- i=4(值5):栈空,ans[4]=0,压入5
- i=3(值3):栈顶5>3,ans[3]=5,压入3
- i=2(值4):栈顶3<4,弹出3;栈顶5>4,ans[2]=5,压入4
- i=1(值7):栈顶4<7,弹出4;栈顶5<7,弹出5;栈空,ans[1]=0,压入7
- i=0(值2):栈顶7>2,ans[0]=7,压入2
- 最终结果:[7, 0, 5, 5, 0]
五、C++ 代码实现(附详细注释)
#include <iostream>
#include <vector>
#include <stack>
using namespace std;// 链表节点定义
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> nextLargerNodes(ListNode* head) {// 第一步:将链表转换为数组vector<int> nums;ListNode* current = head;while (current != nullptr) {nums.push_back(current->val);current = current->next;}int n = nums.size();vector<int> result(n, 0); // 初始化结果数组,全部为0stack<int> st; // 单调栈,存储数组索引// 第二步:使用单调栈找到下一个更大节点for (int i = 0; i < n; i++) {// 当栈不为空且当前元素大于栈顶元素时while (!st.empty() && nums[i] > nums[st.top()]) {// 找到了栈顶元素的下一个更大节点result[st.top()] = nums[i];st.pop();}// 将当前索引入栈st.push(i);}return result;}
};// 辅助函数:打印数组
void printVector(const vector<int>& vec) {for (int num : vec) {cout << num << " ";}cout << endl;
}// 测试代码
int main() {// 构建示例链表:2->7->4->3->5ListNode* head = new ListNode(2);head->next = new ListNode(7);head->next->next = new ListNode(4);head->next->next->next = new ListNode(3);head->next->next->next->next = new ListNode(5);Solution solution;vector<int> result = solution.nextLargerNodes(head);printVector(result); // 输出:7 0 5 5 0// 释放内存while (head != nullptr) {ListNode* temp = head;head = head->next;delete temp;}return 0;
}
六、时间空间复杂度
- 时间复杂度:O(n),其中n是链表长度。每个元素最多入栈一次、出栈一次。
- 空间复杂度:O(n),用于存储数组和栈,最坏情况下栈的大小为n。
七、注意事项
- 单调栈性质:栈中元素保持单调递减,这样当遇到更大的元素时,可以确定栈中哪些元素找到了下一个更大节点。
- 索引存储:栈中存储的是索引而不是值,这样我们可以知道结果应该放在哪个位置。
- 边界处理:处理空链表的情况,以及链表只有一个节点的情况。
- 结果初始化:结果数组初始化为0,这样如果没有找到更大节点,结果自然就是0。
- 严格大于:题目要求严格大于,所以比较时使用
>
而不是>=
。 - 链表转数组:先将链表转换为数组可以简化索引管理,如果不想转换,也可以直接处理链表,但需要记录每个节点的位置信息。