【算法】1019.链表中的下一个更大节点--通俗讲解

49 阅读4分钟

一、题目是啥?一句话说清

给定一个链表,对于每个节点,找到它后面第一个值比它大的节点,如果没有则返回0。

示例:

  • 输入:2 → 7 → 4 → 3 → 5
  • 输出:[7, 0, 5, 5, 0]

二、解题核心

使用单调栈:遍历链表,维护一个单调递减的栈,当遇到比栈顶元素大的节点时,说明找到了栈顶元素的下一个更大节点。

这就像排队时,每个人都在找前面第一个比自己高的人,如果找到了就记住他的身高,找不到就记0。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 单调栈的使用

  • 是什么:维护一个栈,栈中存储的是节点的索引和值,保持栈中元素的值单调递减。
  • 为什么重要:单调栈可以高效地找到每个元素的下一个更大元素,时间复杂度为O(n)。

2. 栈中存储索引

  • 是什么:在栈中存储节点的索引(位置),而不是节点本身。
  • 为什么重要:这样我们可以知道当前节点对应结果数组中的哪个位置,从而正确设置结果值。

3. 逆序处理

  • 是什么:将链表转换为数组后,从后往前处理,或者使用栈来模拟逆序处理。
  • 为什么重要:因为要找的是"下一个"更大节点,从后往前处理可以确保我们处理当前节点时,已经知道了后面所有节点的信息。

四、看图理解流程(通俗理解版本)

假设链表为:2 → 7 → 4 → 3 → 5

  1. 转换为数组:[2, 7, 4, 3, 5]
  2. 初始化:结果数组ans = [0, 0, 0, 0, 0],栈stack为空
  3. 从后往前处理
    • 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
  4. 最终结果:[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); // 初始化结果数组,全部为0
        stack<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->5
    ListNode* 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。
  • 严格大于:题目要求严格大于,所以比较时使用>而不是>=
  • 链表转数组:先将链表转换为数组可以简化索引管理,如果不想转换,也可以直接处理链表,但需要记录每个节点的位置信息。