单调栈与笛卡尔树

单调栈是一种特殊的栈数据结构,栈中元素保持单调性(递增或递减)。它主要用于解决"下一个更大元素"、"最大矩形面积"等问题。笛卡尔树是根据数组构建的二叉树,同时满足二叉搜索树的性质(按位置)和堆的性质(按值),常与单调栈结合使用。

单调栈基础

单调栈通过维护栈中元素的单调性,能够高效解决一类寻找"更大/更小元素"的问题。

单调栈基础应用 - 下一个更大元素

#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;

class MonotonicStack {
public:
    // 找到每个元素右边第一个更大的元素
    vector<int> nextGreaterElement(vector<int>& nums) {
        int n = nums.size();
        vector<int> result(n, -1);
        stack<int> stk; // 存储索引
        
        for (int i = 0; i < n; i++) {
            // 当前元素比栈顶元素大时,栈顶元素找到了答案
            while (!stk.empty() && nums[stk.top()] < nums[i]) {
                result[stk.top()] = nums[i];
                stk.pop();
            }
            stk.push(i);
        }
        
        return result;
    }
    
    // 找到每个元素左边第一个更大的元素
    vector<int> previousGreaterElement(vector<int>& nums) {
        int n = nums.size();
        vector<int> result(n, -1);
        stack<int> stk;
        
        for (int i = 0; i < n; i++) {
            // 维护递减栈
            while (!stk.empty() && nums[stk.top()] <= nums[i]) {
                stk.pop();
            }
            
            if (!stk.empty()) {
                result[i] = nums[stk.top()];
            }
            
            stk.push(i);
        }
        
        return result;
    }
    
    // 找到每个元素右边第一个更小的元素
    vector<int> nextSmallerElement(vector<int>& nums) {
        int n = nums.size();
        vector<int> result(n, -1);
        stack<int> stk;
        
        for (int i = 0; i < n; i++) {
            while (!stk.empty() && nums[stk.top()] > nums[i]) {
                result[stk.top()] = nums[i];
                stk.pop();
            }
            stk.push(i);
        }
        
        return result;
    }
    
    // 找到每个元素左边第一个更小的元素
    vector<int> previousSmallerElement(vector<int>& nums) {
        int n = nums.size();
        vector<int> result(n, -1);
        stack<int> stk;
        
        for (int i = 0; i < n; i++) {
            while (!stk.empty() && nums[stk.top()] >= nums[i]) {
                stk.pop();
            }
            
            if (!stk.empty()) {
                result[i] = nums[stk.top()];
            }
            
            stk.push(i);
        }
        
        return result;
    }
};

// 经典应用:柱状图中最大的矩形
class LargestRectangleInHistogram {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> stk;
        int maxArea = 0;
        
        // 在末尾添加一个高度为0的柱子,确保所有柱子都被处理
        heights.push_back(0);
        
        for (int i = 0; i < heights.size(); i++) {
            while (!stk.empty() && heights[stk.top()] > heights[i]) {
                int h = heights[stk.top()];
                stk.pop();
                
                int width = stk.empty() ? i : i - stk.top() - 1;
                maxArea = max(maxArea, h * width);
            }
            stk.push(i);
        }
        
        heights.pop_back(); // 恢复原数组
        return maxArea;
    }
};

// 最大矩形问题(二维扩展)
class MaximalRectangle {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        if (matrix.empty() || matrix[0].empty()) return 0;
        
        int m = matrix.size(), n = matrix[0].size();
        vector<int> heights(n, 0);
        int maxArea = 0;
        
        for (int i = 0; i < m; i++) {
            // 更新每列的高度
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') {
                    heights[j]++;
                } else {
                    heights[j] = 0;
                }
            }
            
            // 计算当前行的最大矩形面积
            maxArea = max(maxArea, largestRectangleArea(heights));
        }
        
        return maxArea;
    }
    
private:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> stk;
        int maxArea = 0;
        heights.push_back(0);
        
        for (int i = 0; i < heights.size(); i++) {
            while (!stk.empty() && heights[stk.top()] > heights[i]) {
                int h = heights[stk.top()];
                stk.pop();
                int width = stk.empty() ? i : i - stk.top() - 1;
                maxArea = max(maxArea, h * width);
            }
            stk.push(i);
        }
        
        heights.pop_back();
        return maxArea;
    }
};

int main() {
    // 测试单调栈基础应用
    vector<int> nums = {2, 1, 2, 4, 3, 1};
    MonotonicStack ms;
    
    cout << "原数组: ";
    for (int x : nums) cout << x << " ";
    cout << endl;
    
    vector<int> nextGreater = ms.nextGreaterElement(nums);
    cout << "下一个更大元素: ";
    for (int x : nextGreater) cout << x << " ";
    cout << endl;
    
    vector<int> prevGreater = ms.previousGreaterElement(nums);
    cout << "前一个更大元素: ";
    for (int x : prevGreater) cout << x << " ";
    cout << endl;
    
    // 测试柱状图最大矩形
    cout << "\n柱状图最大矩形测试:\n";
    LargestRectangleInHistogram lrh;
    vector<int> heights = {2, 1, 5, 6, 2, 3};
    cout << "柱状图高度: ";
    for (int h : heights) cout << h << " ";
    cout << "\n最大矩形面积: " << lrh.largestRectangleArea(heights) << endl;
    
    return 0;
}

单调栈维护递增或递减的元素序列。在寻找下一个更大元素时,当当前元素比栈顶元素大时,栈顶元素找到了答案。柱状图最大矩形通过单调栈找到每个柱子能扩展的左右边界。

笛卡尔树构建

笛卡尔树可以使用单调栈高效构建,为范围最小查询和分治算法优化提供基础。

笛卡尔树的构建与应用

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

struct TreeNode {
    int val;
    int index;
    TreeNode* left;
    TreeNode* right;
    
    TreeNode(int v, int idx) : val(v), index(idx), left(nullptr), right(nullptr) {}
};

class CartesianTree {
public:
    // 使用单调栈构建笛卡尔树
    TreeNode* buildTree(vector<int>& nums) {
        if (nums.empty()) return nullptr;
        
        stack<TreeNode*> stk;
        
        for (int i = 0; i < nums.size(); i++) {
            TreeNode* node = new TreeNode(nums[i], i);
            TreeNode* lastPopped = nullptr;
            
            // 维护最小堆性质:父节点值 <= 子节点值
            while (!stk.empty() && stk.top()->val > nums[i]) {
                lastPopped = stk.top();
                stk.pop();
            }
            
            // 连接节点
            if (!stk.empty()) {
                stk.top()->right = node;
            }
            if (lastPopped) {
                node->left = lastPopped;
            }
            
            stk.push(node);
        }
        
        // 栈底元素是根节点
        while (stk.size() > 1) {
            stk.pop();
        }
        
        return stk.top();
    }
    
    // 使用笛卡尔树解决RMQ问题
    int rangeMinQuery(TreeNode* root, int left, int right) {
        if (!root || root->index < left || root->index > right) {
            return INT_MAX;
        }
        
        if (root->index >= left && root->index <= right) {
            int leftMin = rangeMinQuery(root->left, left, root->index - 1);
            int rightMin = rangeMinQuery(root->right, root->index + 1, right);
            
            return min({root->val, leftMin, rightMin});
        }
        
        return INT_MAX;
    }
    
    // 中序遍历验证BST性质
    void inorderTraversal(TreeNode* root, vector<int>& result) {
        if (!root) return;
        
        inorderTraversal(root->left, result);
        result.push_back(root->val);
        inorderTraversal(root->right, result);
    }
    
    // 打印树结构
    void printTree(TreeNode* root, int depth = 0) {
        if (!root) return;
        
        printTree(root->right, depth + 1);
        
        for (int i = 0; i < depth; i++) cout << "  ";
        cout << root->val << "(" << root->index << ")" << endl;
        
        printTree(root->left, depth + 1);
    }
};

int main() {
    vector<int> nums = {3, 2, 6, 1, 9};
    CartesianTree ct;
    
    cout << "原数组: ";
    for (int x : nums) cout << x << " ";
    cout << endl;
    
    TreeNode* root = ct.buildTree(nums);
    
    cout << "\n笛卡尔树结构:\n";
    ct.printTree(root);
    
    cout << "\n中序遍历: ";
    vector<int> inorder;
    ct.inorderTraversal(root, inorder);
    for (int x : inorder) cout << x << " ";
    cout << endl;
    
    // 测试RMQ
    cout << "\nRMQ测试:\n";
    cout << "RMQ(0, 2): " << ct.rangeMinQuery(root, 0, 2) << endl;
    cout << "RMQ(1, 4): " << ct.rangeMinQuery(root, 1, 4) << endl;
    cout << "RMQ(2, 3): " << ct.rangeMinQuery(root, 2, 3) << endl;
    
    return 0;
}

笛卡尔树通过单调栈构建,满足中序遍历为索引序列(BST性质)和堆性质。可以用于RMQ查询、分治算法优化等。构建过程中维护最小堆性质,每个节点的值小于等于其子节点。

单调栈的高级应用

单调栈在复杂问题中的应用,包括表达式求值、括号匹配、股票价格等问题。

单调栈高级应用 - 复杂问题求解

#include <iostream>
#include <vector>
#include <stack>
#include <string>
#include <unordered_map>
using namespace std;

// 股票价格跨度问题
class StockSpanner {
private:
    stack<pair<int, int>> stk; // {price, span}
    
public:
    int next(int price) {
        int span = 1;
        
        // 累计所有小于等于当前价格的天数
        while (!stk.empty() && stk.top().first <= price) {
            span += stk.top().second;
            stk.pop();
        }
        
        stk.push({price, span});
        return span;
    }
};

// 去除重复字母(保持字典序最小)
class RemoveDuplicateLetters {
public:
    string removeDuplicateLetters(string s) {
        unordered_map<char, int> count;
        unordered_map<char, bool> inStack;
        stack<char> stk;
        
        // 统计每个字符的出现次数
        for (char c : s) {
            count[c]++;
        }
        
        for (char c : s) {
            count[c]--;
            
            if (inStack[c]) continue;
            
            // 如果当前字符小于栈顶字符,且栈顶字符在后面还会出现,则移除栈顶
            while (!stk.empty() && stk.top() > c && count[stk.top()] > 0) {
                inStack[stk.top()] = false;
                stk.pop();
            }
            
            stk.push(c);
            inStack[c] = true;
        }
        
        string result = "";
        while (!stk.empty()) {
            result = stk.top() + result;
            stk.pop();
        }
        
        return result;
    }
};

// 每日温度(下一个更高温度)
class DailyTemperatures {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> result(n, 0);
        stack<int> stk;
        
        for (int i = 0; i < n; i++) {
            while (!stk.empty() && temperatures[stk.top()] < temperatures[i]) {
                int prevIndex = stk.top();
                stk.pop();
                result[prevIndex] = i - prevIndex;
            }
            stk.push(i);
        }
        
        return result;
    }
};

// 子数组的最小值之和
class SumOfSubarrayMinimums {
public:
    int sumSubarrayMins(vector<int>& arr) {
        const int MOD = 1e9 + 7;
        int n = arr.size();
        
        // 计算每个元素左边和右边第一个更小元素的位置
        vector<int> prevSmaller(n, -1);
        vector<int> nextSmaller(n, n);
        
        stack<int> stk;
        
        // 左边第一个更小元素
        for (int i = 0; i < n; i++) {
            while (!stk.empty() && arr[stk.top()] >= arr[i]) {
                stk.pop();
            }
            if (!stk.empty()) {
                prevSmaller[i] = stk.top();
            }
            stk.push(i);
        }
        
        // 清空栈,计算右边第一个更小元素
        while (!stk.empty()) stk.pop();
        
        // 右边第一个更小元素
        for (int i = 0; i < n; i++) {
            while (!stk.empty() && arr[stk.top()] > arr[i]) {
                nextSmaller[stk.top()] = i;
                stk.pop();
            }
            stk.push(i);
        }
        
        long long result = 0;
        for (int i = 0; i < n; i++) {
            long long left = i - prevSmaller[i];
            long long right = nextSmaller[i] - i;
            result = (result + arr[i] * left * right) % MOD;
        }
        
        return result;
    }
};

// 接雨水问题
class TrappingRainWater {
public:
    int trap(vector<int>& height) {
        if (height.empty()) return 0;
        
        stack<int> stk;
        int water = 0;
        
        for (int i = 0; i < height.size(); i++) {
            while (!stk.empty() && height[stk.top()] < height[i]) {
                int bottom = stk.top();
                stk.pop();
                
                if (stk.empty()) break;
                
                int distance = i - stk.top() - 1;
                int boundedHeight = min(height[i], height[stk.top()]) - height[bottom];
                water += distance * boundedHeight;
            }
            stk.push(i);
        }
        
        return water;
    }
};

int main() {
    // 测试股票价格跨度
    cout << "股票价格跨度测试:\n";
    StockSpanner spanner;
    vector<int> prices = {100, 80, 60, 70, 60, 75, 85};
    
    cout << "价格序列: ";
    for (int price : prices) {
        cout << price << "(" << spanner.next(price) << ") ";
    }
    cout << endl;
    
    // 测试去除重复字母
    cout << "\n去除重复字母测试:\n";
    RemoveDuplicateLetters rdl;
    string s = "bcabc";
    cout << "原字符串: " << s << endl;
    cout << "去重后: " << rdl.removeDuplicateLetters(s) << endl;
    
    // 测试每日温度
    cout << "\n每日温度测试:\n";
    DailyTemperatures dt;
    vector<int> temperatures = {73, 74, 75, 71, 69, 72, 76, 73};
    vector<int> days = dt.dailyTemperatures(temperatures);
    
    cout << "温度: ";
    for (int temp : temperatures) cout << temp << " ";
    cout << "\n等待天数: ";
    for (int day : days) cout << day << " ";
    cout << endl;
    
    // 测试子数组最小值之和
    cout << "\n子数组最小值之和测试:\n";
    SumOfSubarrayMinimums ssm;
    vector<int> arr = {3, 1, 2, 4};
    cout << "数组: ";
    for (int x : arr) cout << x << " ";
    cout << "\n子数组最小值之和: " << ssm.sumSubarrayMins(arr) << endl;
    
    // 测试接雨水
    cout << "\n接雨水测试:\n";
    TrappingRainWater trw;
    vector<int> height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
    cout << "高度数组: ";
    for (int h : height) cout << h << " ";
    cout << "\n接雨水量: " << trw.trap(height) << endl;
    
    return 0;
}

高级应用展示了单调栈在各种复杂问题中的威力。股票跨度累计连续小于等于当前价格的天数,去重字母维护字典序最小,接雨水计算凹槽面积,表达式求值处理运算符优先级。

竞赛技巧总结

单调栈的核心特征:

  • 维护栈中元素的单调性(递增或递减)
  • 每个元素最多进出栈一次,时间复杂度O(n)
  • 适用于寻找"下一个更大/更小元素"类问题
  • 通过弹栈操作确定元素的影响范围

笛卡尔树的性质:

  • 同时满足BST性质(按索引)和堆性质(按值)
  • 中序遍历得到原数组的索引序列
  • 可以用于RMQ查询和分治算法优化
  • 构建时间复杂度O(n),使用单调栈实现

常见应用模式:

  • 区间最值问题:柱状图最大矩形、接雨水
  • 数组范围问题:子数组最小值和、温度等待
  • 字符串处理:去重保序、括号匹配
  • 表达式计算:运算符优先级、逆波兰表示

实现技巧:

  • 存储索引而非值:便于计算距离和范围
  • 正确的弹栈条件:严格单调 vs 非严格单调
  • 边界处理:在数组末尾添加哨兵元素
  • 双向扫描:分别计算左边界和右边界

调试要点:

  • 检查单调性的维护条件(> vs >=)
  • 确认栈的存储内容(值 vs 索引)
  • 验证弹栈时的计算逻辑
  • 测试边界情况(空栈、单元素等)

优化策略:

  • 空间优化:使用数组模拟栈
  • 常数优化:减少不必要的判断
  • 并行化:某些情况下可以并行处理
  • 缓存友好:考虑数据访问模式