Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,32 +164,276 @@ tags:

<!-- solution:start -->

### 方法一
### 方法一:双 Deque 维护窗口最大值与最小值
本题要求计算所有长度不超过 $k$ 的子数组中:
* 子数组最大值之和
* 子数组最小值之和

并返回两者之和。

我们定义:

$$
MaxSum_i = \sum_{j = \max(0, i - k + 1)}^i \max(nums[j:i + 1])
$$

表示所有以索引 $i$ 结尾的合法子数组,它们各自最大值的总和。

同理:

$$
MinSum_i = \sum_{j = \max(0, i - k + 1)}^i \min(nums[j:i + 1])
$$

表示所有以索引 $i$ 结尾的合法子数组,它们各自最小值的总和。

接下来,我们维护两个单调队列:
* `max_stack`:维护当前窗口内所有子数组可能的最大值。
* `min_stack`:维护当前窗口内所有子数组可能的最小值。

虽然底层数据结构为 `deque`,但由于绝大多数操作都发生在右侧,因此其本质仍然是单调栈。

队列中的每个元素格式为:
```text
(index, value, shares)
```

其中:
* `index`:元素索引
* `value`:元素值
* `shares`:该元素在当前窗口内,作为子数组最大值 / 最小值的贡献次数

---
#### Step 1. 边界管理
当遍历到当前元素 `nums[i]` 时,我们首先需要确保窗口长度不超过 $k$。

窗口左边界为:

$$
\max(0, i - k + 1)
$$

若某元素索引已经小于该边界,说明其已经离开窗口,不再属于任何合法子数组。

因此:
* `max_stack` 栈头元素若已越界,则弹出
* `min_stack` 栈头元素若已越界,则弹出

同时,需要从:
* `subarrays_max_sum`
* `subarrays_min_sum`

中扣除这些元素对应的贡献。

---
#### Step 2. 更新单调栈
对于当前元素 `nums[i]`:

若 `max_stack` 栈尾元素的值小于等于 `nums[i]`:

说明这些元素已经不可能继续成为后续子数组的最大值。

因此:
1. 持续弹出这些元素
2. 将它们的贡献次数 `shares` 转移给 `nums[i]`
3. 更新 `subarrays_max_sum`

若弹出的元素为:
```text
(prev_idx, prev_num, prev_shares)
```

则会对:
```text
subarrays_max_sum
```

产生:

$$
(nums[i] - prev\_num) \times prev\_shares
$$

的净增量。

随后,再加上当前元素自身形成的新子数组贡献。

最后,将:
```text
(i, nums[i], shares)
```
压入 `max_stack`。

---
`min_stack` 的操作方式完全相同。区别仅在于:
* `max_stack` 维护单调递减性质
* `min_stack` 维护单调递增性质

因此比较条件需要反转:
```text
max_stack: top_value <= nums[i]
min_stack: top_value >= nums[i]
```

---
#### Step 3. 累加答案
当索引 $i$ 处理完成后:

* `subarrays_max_sum`
表示所有以 $i$ 结尾的合法子数组最大值总和
* `subarrays_min_sum`
表示所有以 $i$ 结尾的合法子数组最小值总和

因此我们将两者加入最终答案:
```text
subarrays_max_min_sum
```
最终返回该结果即可。

---
时间复杂度为 $O(n)$,空间复杂度为 $O(n)$。

这是因为:
* 每个元素至多进入并离开 `max_stack` 各一次
* 每个元素至多进入并离开 `min_stack` 各一次

因此每个元素总操作次数不超过四次,整体时间复杂度为严格线性时间复杂度。

<!-- tabs:start -->

#### Python3

```python
class Solution:
def minMaxSubarraySum(self, nums: list[int], k: int) -> int:
subarrays_max_min_sum = 0

```
max_stack: deque[list[int]] = deque([]) # Format: [idx, num, shares].
subarrays_max_sum = 0

min_stack: deque[list[int]] = deque([]) # Format: [idx, num, shares].
subarrays_min_sum = 0

for end_idx, num in enumerate(nums):
start_idx = max(0, end_idx - k + 1)

# Window start idx slides by 1: must update stacks' info.
if start_idx > 0:
max_stack[0][2] -= 1 # Decrement stack's front num shares.
subarrays_max_sum -= max_stack[0][1]

if max_stack[0][0] < start_idx: # Front num out of window.
max_stack.popleft()

min_stack[0][2] -= 1 # Decrement stack's front num shares.
subarrays_min_sum -= min_stack[0][1]

if min_stack[0][0] < start_idx: # Front num out of window.
min_stack.popleft()

max_shares = 1 # Base case.
subarrays_max_sum += num

while max_stack and max_stack[-1][1] <= num:
_, prev_num, prev_shares = max_stack.pop()

max_shares += prev_shares # Max shares transition.

# Reflect transition in max sum.
subarrays_max_sum += (num - prev_num) * prev_shares

max_stack.append([end_idx, num, max_shares])

min_shares = 1 # Base case.
subarrays_min_sum += num

while min_stack and min_stack[-1][1] >= num:
_, prev_num, prev_shares = min_stack.pop()

min_shares += prev_shares # Min shares transition.

# Reflect transition in min sum.
subarrays_min_sum += (num - prev_num) * prev_shares

#### Java
min_stack.append([end_idx, num, min_shares])

```java
subarrays_max_min_sum += subarrays_max_sum + subarrays_min_sum

return subarrays_max_min_sum
```

#### C++

```cpp
class Solution {
public:
long long minMaxSubarraySum(vector<int>& nums, int k) {
long long totalMaxMinSum = 0;
long long windowMaxSum = 0, windowMinSum = 0;

```
// Format: {idx, num, shares}. Use long long to prevent overflow.
deque<tuple<int, int, long long>> maxStack, minStack;

for (int endIdx = 0; endIdx < nums.size(); endIdx++) {
int startIdx = max(0, endIdx - k + 1);

// Window start idx slides by 1: must update stacks' info.
if (startIdx > 0) {
get<2>(maxStack.front())--; // Decrement stack's front num shares.
windowMaxSum -= get<1>(maxStack.front());

#### Go
// Front num out of window.
if (get<0>(maxStack.front()) < startIdx)
maxStack.pop_front();

get<2>(minStack.front())--; // Decrement stack's front num shares.
windowMinSum -= get<1>(minStack.front());

// Front num out of window.
if (get<0>(minStack.front()) < startIdx)
minStack.pop_front();
}

```go
long long num = nums[endIdx];

long long maxShares = 1; // Base case.
windowMaxSum += num;

while (!maxStack.empty() && get<1>(maxStack.back()) <= num) {
int prevNum = get<1>(maxStack.back());
long long prevShares = get<2>(maxStack.back());
maxStack.pop_back();

maxShares += prevShares; // Max shares transition.

// Reflect transition in max sum.
windowMaxSum += (num - prevNum) * prevShares;
}

maxStack.push_back({endIdx, num, maxShares});

long long minShares = 1; // Base case.
windowMinSum += num;

while (!minStack.empty() && get<1>(minStack.back()) >= num) {
int prevNum = get<1>(minStack.back());
long long prevShares = get<2>(minStack.back());
minStack.pop_back();

minShares += prevShares; // Min shares transition.

// Reflect transition in min sum.
windowMinSum += (num - prevNum) * prevShares;
}

minStack.push_back({endIdx, num, minShares});

totalMaxMinSum += windowMaxSum + windowMinSum;
}

return totalMaxMinSum;
}
};
```

#### JavaScript
Expand Down
Loading
Loading