【算法】【单调栈】使用双单调栈,解决力扣2866. 美丽塔 II

力扣2866. 美丽塔 II


【算法】【单调栈】使用双单调栈,解决力扣2866. 美丽塔 II

前言

本文介绍了一种使用双单调栈解决力扣 2866 题《美丽塔 II》的算法。该算法旨在找到满足一定条件的美丽塔方案,其中美丽塔的定义涉及到山脉数组的特性。文章采用 C++ 实现,并通过详细的注释解释了每个步骤的原理和实现方式。

如果不熟悉单调栈,请先阅读关于单调栈的简介。

单调栈简介及应用示例

单调栈简介

单调栈是一种基于栈的数据结构,主要用于解决一些与单调性相关的问题。它通常分为单调递增栈和单调递减栈两种类型。单调递增栈即栈内元素从栈底到栈顶呈递增顺序,而单调递减栈则相反。

单调栈的核心思想是,通过维护一个栈,确保栈内元素的单调性,从而在入栈或出栈操作中,通过一定的规则实现高效的数据处理。单调栈在解决一些与局部最值相关的问题时非常实用,例如找到下一个更大元素、下一个更小元素等。

单调递增栈示例

让我们以一个简单的问题为例,说明单调递增栈的应用:找到数组中每个元素的下一个更大元素。

问题描述

给定一个整数数组 nums,要求对于数组中的每个元素,找到其右侧第一个比它大的元素。如果不存在,则用 -1 表示。

解决思路

使用单调递增栈,遍历数组元素。如果当前元素比栈顶元素大,说明找到了栈顶元素的下一个更大元素,将栈顶元素出栈并记录结果;否则,将当前元素入栈。

代码实现
#include <iostream>
#include <vector>
#include <stack>

using namespace std;

vector<int> nextGreaterElements(const vector<int>& nums) {
    int n = nums.size();
    vector<int> result(n, -1);  // 初始化结果数组为-1
    stack<int> monoStack;  // 单调递增栈,存储元素下标

    for (int i = 0; i < n; ++i) {
        // 当栈非空且当前元素大于栈顶元素时,栈顶元素的下一个更大元素就是当前元素
        while (!monoStack.empty() && nums[i] > nums[monoStack.top()]) {
            result[monoStack.top()] = nums[i];
            monoStack.pop();
        }
        // 当前元素入栈
        monoStack.push(i);
    }

    return result;
}

int main() {
    vector<int> nums = {2, 4, 3, 1, 5, 6};
    vector<int> result = nextGreaterElements(nums);

    cout << "Original Array: ";
    for (int num : nums) {
        cout << num << " ";
    }

    cout << "\nNext Greater Elements: ";
    for (int val : result) {
        cout << val << " ";
    }

    return 0;
}
输出结果
Original Array: 2 4 3 1 5 6 
Next Greater Elements: 4 5 5 5 6 -1 

结论

单调栈是一种强大的数据结构,通过合理的设计和运用,可以解决多种问题,提高算法的效率。在本文中,我们通过一个简单的示例演示了单调递增栈的应用,希望读者能在实际问题中更好地理解和运用单调栈。

题目描述

给定一个长度为 n 的整数数组 maxHeights,任务是在坐标轴上建 n 座塔。每座塔的高度为 heights[i],满足以下条件:

  1. 1 <= heights[i] <= maxHeights[i]
  2. heights 是一个山脉数组

山脉数组的定义:

  • 对于所有 0 < j <= i,都有 heights[j - 1] <= heights[j]
  • 对于所有 i <= k < n - 1,都有 heights[k + 1] <= heights[k]

任务要求找到满足美丽塔条件的方案中,高度和的最大值。

解决思路

解决这个问题的思路是通过前后缀分解,构建以当前位置 i 为最大高度的右侧高度和以及左侧高度和。

构建右侧高度和

逆序遍历 maxHeights,使用一个单调递增的栈 suf_stk 记录右侧仍合法的高度,同时使用一个 long long 类型的变量 sum 存储右侧高度的加和结果。在逆序遍历的过程中,栈的目的是保持栈内元素单调递增,即满足右侧高度不超过当前高度。通过撤回操作,确保只有合法的高度加入到 sum 中,最终得到以各个高度为最大值时的右侧高度和,即 suf[i] 为以 maxHeights[i] 为最大高度时的右侧高度和。

// 构建右侧高度和
deque<int> suf_stk;
suf_stk.push_front(n); // n是一个哨兵,可以很方便地进行下面的撤回操作
vector<long long> suf(n + 1, 0); // 存储以 suf[i] 为山顶时,右侧的递减的高度的总和。
long long sum = 0;
for (int i = n - 1; i >= 0; --i) {
    long long curr_mx_h = maxHeights[i];
    while (suf_stk.size() > 1 && maxHeights[suf_stk.back()] > curr_mx_h) {
        int j = suf_stk.back();
        suf_stk.pop_back();
        // 减去[ j, suf_stk.back() )这个区间的高度
        sum -= (long long) maxHeights[j] * (suf_stk.back() - j);
    }
    // 给[ i, suf_stk.back() )这个区间添加当前可接受的最大高度。
    sum += (long long) curr_mx_h * (suf_stk.back() - i);
    suf[i] = sum;
    suf_stk.push_back(i);
}

构建左侧高度和

正序遍历 maxHeights,使用一个单调递增的栈 pre_stk 记录左侧仍合法的高度。同时,使用一个 long long 变量 pre 存储左侧高度的加和结果。在正序遍历的过程中,栈的目的同样是保持栈内元素单调递增,确保左侧高度不超过当前高度。通过当前计算到的左侧高度和与之前计算得到的右侧高度和来获取最大答案,更新答案的值。

// 构建左侧高度和
deque<int> pre_stk;
pre_stk.push_front(-1); // -1是一个哨兵
long long ans = 0, pre = 0;
for (int i = 0; i < n; ++i) {
    long long curr_num = maxHeights[i];
    while (pre_stk.size() > 1 && maxHeights[pre_stk.back()] > curr_num) {
        int j = pre_stk.back();
        pre_stk.pop_back();
        pre -= (long long) maxHeights[j] * (j - pre_stk.back());
    }
    pre += (long long) maxHeights[i] * (i - pre_stk.back());
    ans = max(ans, pre + suf[i + 1]);
    pre_stk.push_back(i);
}

完整代码

下面是完整的 C++ 代码:

class Solution {
public:
    long long maximumSumOfHeights(vector<int>& maxHeights) {
        int n = maxHeights.size();

        // 构建右侧高度和
        deque<int> suf_stk;
        suf_stk.push_front(n); // n是一个哨兵,可以很方便地进行下面的撤回操作
        vector<long long> suf(n + 1, 0); // 存储以 suf[i] 为山顶时,右侧的递减的高度的总和。
        long long sum = 0;
        for (int i = n - 1; i >= 0; --i) {
            long long curr_mx_h = maxHeights[i];
            while (suf_stk.size() > 1 && maxHeights[suf_stk.back()] > curr_mx_h) {
                int j = suf_stk.back();
                suf_stk.pop_back();
                // 减去[ j, suf_stk.back() )这个区间的高度
                sum -= (long long) maxHeights[j] * (suf_stk.back() - j);

 
            }
            // 给[ i, suf_stk.back() )这个区间添加当前可接受的最大高度。
            sum += (long long) curr_mx_h * (suf_stk.back() - i);
            suf[i] = sum;
            suf_stk.push_back(i);
        }

        // 构建左侧高度和
        deque<int> pre_stk;
        pre_stk.push_front(-1); // -1是一个哨兵
        long long ans = 0, pre = 0;
        for (int i = 0; i < n; ++i) {
            long long curr_num = maxHeights[i];
            while (pre_stk.size() > 1 && maxHeights[pre_stk.back()] > curr_num) {
                int j = pre_stk.back();
                pre_stk.pop_back();
                pre -= (long long) maxHeights[j] * (j - pre_stk.back());
            }
            pre += (long long) maxHeights[i] * (i - pre_stk.back());
            ans = max(ans, pre + suf[i + 1]);
            pre_stk.push_back(i);
        }

        return ans;
    }
};

结论

本文介绍了一种使用双单调栈解决力扣 2866 题的算法,并提供了详细的代码注释。通过构建右侧高度和和左侧高度和,最终得到了满足美丽塔条件的方案中高度和的最大值。该算法在实现上采用了单调栈的思想,通过栈的维护和撤回操作,使得高度和的计算更加高效。

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值