2865. 美丽塔 I
在 第 i 处建塔的高度 heights[i] 必须在 [1, maxHeight[i]] 之间 题目给了 maxHeight[i] 需要最大值,因此可以维护一个单调栈,令 heights[i] = maxHeight[i], 而 i 之前的 数为 单调栈中比 maxHeight[i] 小些的数。
以 示例3 为例:
maxHeights = [3,2,5,5,2,3]
总体原则: 只能有一个峰头 左右改 峰位置左边非严格递增, 右边非严格递减。
3 : heights = [3]
2: 比栈顶3 小,由于要满足 由于 满足 heights[i] <= maxHeights[i], heights = [2, 2]5: 5 比 2 大, heights = [2, 2, 5]
5: heights = [2, 2, 5, 5] 可见 要记录 最大值。
2: 比 5 小 heights = [2, 2, 5, 5, 2] ,
3: 比 2 大,但又不大于 5,无法成为新的峰位置。而
峰位置的右侧只能非严格递减。所以只能为 2
代码实现的时候发现一个问题,要是前面有一个峰值 a ,后面又碰到 一次 a , 同样是这个峰值,那到底 这个 a 在 前面的收益高 还是在 后面的位置收益高,所以应该每个 最高值 a 都要计算相应的 峰值。
- 这个还是有问题
比如 这个示例, 用 次大值 结果更大
Python3
方法1: 枚举 每个元素作为山顶
易于理解 参考链接
class Solution:
def maximumSumOfHeights(self, maxHeights: List[int]) -> int:
res = 0
n = len(maxHeights)
for i in range(n):
curSum = maxHeights[i]
## 处理 峰值位置 之后的数据
pre = maxHeights[i]
for j in range(i + 1, n): # 从左到右 不严格递减
pre = min(pre, maxHeights[j])
curSum += pre
## 处理 峰值位置 之前的数据
pre = maxHeights[i]
for j in range(i-1, -1, -1): # 从右到左 不严格递减
pre = min(pre, maxHeights[j])
curSum += pre
res = max(res, curSum)
return res
模拟 参考链接
看到这个 先创建 heights 列表再 求和的方法, 内存加大。
⭐ 方法2: 前后缀分解 + 单调栈 【空间换时间】
class Solution:
def maximumSumOfHeights(self, maxHeights: List[int]) -> int:
pre, suf, n = [0], [0], len(maxHeights)
stack_pre, stack_suf = [-1], [n] # 求 前后缀 时 分别维护一个单调递增 栈
for i in range(n):
while len(stack_pre) > 1 and maxHeights[stack_pre[-1]] > maxHeights[i]: # 大于 1 是 因为 加了 哨兵
stack_pre.pop() # 去掉 比 当前峰顶 大的
pre.append(pre[stack_pre[-1] + 1] + maxHeights[i] * (i - stack_pre[-1])) # 0 1 2 3 # 前面的前缀和 可以利用
stack_pre.append(i) ## 存储的是 下标
j = n - 1- i
while len(stack_suf) > 1 and maxHeights[stack_suf[-1]] > maxHeights[j]:
stack_suf.pop()
suf.append(suf[n-stack_suf[-1]] + maxHeights[j] * (stack_suf[-1]-j))
stack_suf.append(j)
return max([ pre[i]+suf[n-i] for i in range(n) ])
相当于 利用 之前处理过的部分
class Solution:
def maximumSumOfHeights(self, maxHeights: List[int]) -> int:
n = len(maxHeights)
suf = [0] * (n + 1) ## 存储 以 maxHeight[i] 为 峰 时 右侧的后缀和
pre = [0] * (n + 1) ## 存储 以 maxHeight[i] 为 峰 时 左侧的前缀和
res = 0
# 求 Pre 前缀
stack = [] # 维护 一个单调增的栈 始终是栈顶为 峰 !!!存储的是 下标 只需要知道值
for i in range(n): #
while stack and maxHeights[i] < maxHeights[stack[-1]]: # 当前元素 小于 栈顶了, 要修改栈中元素 遍历栈中 当前元素
stack.pop() # 记录 弹出的最终值的下标
j = stack[-1] if stack else -1
pre[i+1] = pre[j+1] + (i - j) * maxHeights[i]
stack.append(i)
# 求 suf 后缀
stack = []
for i in range(n-1, -1, -1): # 注意 suf 总长为 n + 1
while stack and maxHeights[i] < maxHeights[stack[-1]]: # 当前元素 小于 栈顶了, 要修改栈中元素 遍历栈中 当前元素
stack.pop() # 记录 弹出的最终值的下标
j = stack[-1] if stack else n
suf[i] = suf[j] + (j - i) * maxHeights[i]
stack.append(i)
res = max(res, pre[i+1] + suf[i] - maxHeights[i])
return res
复杂度分析:
- 时间复杂度: 。单层循环。
- 空间复杂度: 。单调栈的空间
C++
方法1: 枚举 每个元素作为山顶
class Solution {
public:
long long maximumSumOfHeights(vector<int>& maxHeights) {
int n = maxHeights.size();
long long res = 0;
for (int i = 0; i < n; ++i){
long long curSum = maxHeights[i];
// 前缀 处理部分
int pre = maxHeights[i];
for (int j = i + 1; j < n; ++j){
pre = min(pre, maxHeights[j]);
curSum += pre;
}
// 后缀 处理部分
pre = maxHeights[i];
for (int j = i - 1;j >= 0; --j){
pre = min(pre, maxHeights[j]);
curSum += pre;
}
res = max(res, curSum);
}
return res;
}
};
⭐ 方法2: 前后缀分解 + 单调栈 【空间换时间】
class Solution {
public:
long long maximumSumOfHeights(vector<int>& maxHeights) {
int n = maxHeights.size();
long long res = 0;
vector<long long> pre(n), suf(n);
stack<int> st_pre, st_suf;
// 处理 前缀 部分
for (int i = 0; i < n; i++) {
while (!st_pre.empty() && maxHeights[i] < maxHeights[st_pre.top()]) {
st_pre.pop();
}
// 跳出 循环 有以下两种 情形, 分别讨论
if (st_pre.empty()) {
pre[i] = (long long)(i + 1) * maxHeights[i]; // 注意 个数 和 索引 之间的关系
} else {
pre[i] = pre[st_pre.top()] + (long long)(i - st_pre.top()) * maxHeights[i];
}
st_pre.emplace(i);
}
// 处理 后缀 部分
for (int i = n - 1; i >= 0; i--) {
while (!st_suf.empty() && maxHeights[i] < maxHeights[st_suf.top()]) {
st_suf.pop();
}
// 跳出 循环 有以下两种 情形, 分别讨论
if (st_suf.empty()) {
suf[i] = (long long)(n - i) * maxHeights[i];
} else {
suf[i] = suf[st_suf.top()] + (long long)(st_suf.top() - i) * maxHeights[i];
}
st_suf.emplace(i);
res = max(res, pre[i] + suf[i] - maxHeights[i]);
}
return res;
}
};
// 参考链接:https://leetcode.cn/problems/beautiful-towers-ii/
2866. 美丽塔 II
一样的题, 区别在于 这道题 的 解法 无法通过。
方法: 前后缀分解 + 单调栈
Python3
class Solution:
def maximumSumOfHeights(self, maxHeights: List[int]) -> int:
pre, suf, n = [0], [0], len(maxHeights)
stack_pre, stack_suf = [-1], [n] # 求 前后缀 时 分别维护一个单调递增 栈
for i in range(n):
while len(stack_pre) > 1 and maxHeights[stack_pre[-1]] > maxHeights[i]: # 大于 1 是 因为 加了 哨兵
stack_pre.pop() # 去掉 比 当前峰顶 大的
pre.append(pre[stack_pre[-1] + 1] + maxHeights[i] * (i - stack_pre[-1])) # 0 1 2 3 # 前面的前缀和 可以利用
stack_pre.append(i) ## 存储的是 下标
j = n - 1- i
while len(stack_suf) > 1 and maxHeights[stack_suf[-1]] > maxHeights[j]:
stack_suf.pop()
suf.append(suf[n-stack_suf[-1]] + maxHeights[j] * (stack_suf[-1]-j))
stack_suf.append(j)
return max([ pre[i]+suf[n-i] for i in range(n) ])
C++
class Solution {
public:
long long maximumSumOfHeights(vector<int>& maxHeights) {
int n = maxHeights.size();
long long res = 0;
vector<long long> pre(n), suf(n);
stack<int> st_pre, st_suf;
// 处理 前缀 部分
for (int i = 0; i < n; i++) {
while (!st_pre.empty() && maxHeights[i] < maxHeights[st_pre.top()]) {
st_pre.pop();
}
// 跳出 循环 有以下两种 情形, 分别讨论
if (st_pre.empty()) {
pre[i] = (long long)(i + 1) * maxHeights[i]; // 注意 个数 和 索引 之间的关系
} else {
pre[i] = pre[st_pre.top()] + (long long)(i - st_pre.top()) * maxHeights[i];
}
st_pre.emplace(i);
}
// 处理 后缀 部分
for (int i = n - 1; i >= 0; i--) {
while (!st_suf.empty() && maxHeights[i] < maxHeights[st_suf.top()]) {
st_suf.pop();
}
// 跳出 循环 有以下两种 情形, 分别讨论
if (st_suf.empty()) {
suf[i] = (long long)(n - i) * maxHeights[i];
} else {
suf[i] = suf[st_suf.top()] + (long long)(st_suf.top() - i) * maxHeights[i];
}
st_suf.emplace(i);
res = max(res, pre[i] + suf[i] - maxHeights[i]);
}
return res;
}
};
// 参考链接:https://leetcode.cn/problems/beautiful-towers-ii/