Medium

题目描述

给你一个由不同元素组成的整数数组 nums

如果 nums 的子数组 nums[l...r] 满足以下条件,则称其为碗形子数组:

  • 子数组长度至少为 3,即 r - l + 1 >= 3
  • 两端元素的最小值严格大于中间所有元素的最大值,即 min(nums[l], nums[r]) > max(nums[l + 1], ..., nums[r - 1])

返回 nums 中碗形子数组的数量。

示例 1:

输入:nums = [2,5,3,1,4]
输出:2
解释:
碗形子数组是 [3, 1, 4] 和 [5, 3, 1, 4]。
- [3, 1, 4] 是碗形,因为 min(3, 4) = 3 > max(1) = 1
- [5, 3, 1, 4] 是碗形,因为 min(5, 4) = 4 > max(3, 1) = 3

示例 2:

输入:nums = [5,1,2,3,4]
输出:3
解释:
碗形子数组是 [5, 1, 2]、[5, 1, 2, 3] 和 [5, 1, 2, 3, 4]。

示例 3:

输入:nums = [1000000000,999999999,999999998]
输出:0
解释:
没有子数组是碗形的。

提示:

  • 3 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • nums 由不同元素组成

解题思路

这道题需要找出满足"两端最小值大于中间最大值"的子数组。关键观察是碗形条件的对称性。

核心思路:

我们可以分两种情况讨论:

  1. 左端点较小:nums[l] < nums[r]nums[l] > max(nums[l+1]...nums[r-1])
  2. 右端点较小:nums[r] < nums[l]nums[r] > max(nums[l+1]...nums[r-1])

单调栈优化:

使用单调栈预处理每个位置:

  • nextGreater[i]:位置 i 右侧第一个大于 nums[i] 的位置
  • prevGreater[i]:位置 i 左侧第一个大于 nums[i] 的位置

对于情况1(左端点较小),我们枚举左端点 l,找到所有满足条件的右端点 r:

  • nums[l] < nums[r](右端点更大)
  • 中间区域 [l+1, r-1] 的所有元素都小于 nums[l]

这意味着区间 [l+1, r-1] 内不能有大于等于 nums[l] 的元素。通过单调栈,我们可以快速确定有效的右端点范围。

算法步骤:

  1. 预处理单调栈,获取每个位置的 nextGreater 和 prevGreater
  2. 枚举左端点,利用单调栈信息快速计算有效右端点数量
  3. 处理两种情况并求和

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

代码实现

class Solution {
public:
    long long bowlSubarrays(vector<int>& nums) {
        int n = nums.size();
        vector<int> nextGreater(n, n), prevGreater(n, -1);
        
        // 单调栈求右侧第一个更大元素
        stack<int> st;
        for (int i = 0; i < n; i++) {
            while (!st.empty() && nums[st.top()] < nums[i]) {
                nextGreater[st.top()] = i;
                st.pop();
            }
            st.push(i);
        }
        
        // 单调栈求左侧第一个更大元素
        while (!st.empty()) st.pop();
        for (int i = n - 1; i >= 0; i--) {
            while (!st.empty() && nums[st.top()] < nums[i]) {
                prevGreater[st.top()] = i;
                st.pop();
            }
            st.push(i);
        }
        
        long long result = 0;
        
        // 情况1:左端点较小
        for (int l = 0; l < n - 2; l++) {
            int rightBound = nextGreater[l];
            for (int r = l + 2; r < rightBound && r < n; r++) {
                if (nums[r] > nums[l]) {
                    result++;
                }
            }
        }
        
        // 情况2:右端点较小
        for (int r = 2; r < n; r++) {
            int leftBound = prevGreater[r];
            for (int l = max(0, leftBound + 1); l <= r - 2; l++) {
                if (nums[l] > nums[r]) {
                    result++;
                }
            }
        }
        
        return result;
    }
};
class Solution:
    def bowlSubarrays(self, nums: List[int]) -> int:
        n = len(nums)
        next_greater = [n] * n
        prev_greater = [-1] * n
        
        # 单调栈求右侧第一个更大元素
        stack = []
        for i in range(n):
            while stack and nums[stack[-1]] < nums[i]:
                next_greater[stack.pop()] = i
            stack.append(i)
        
        # 单调栈求左侧第一个更大元素
        stack = []
        for i in range(n - 1, -1, -1):
            while stack and nums[stack[-1]] < nums[i]:
                prev_greater[stack.pop()] = i
            stack.append(i)
        
        result = 0
        
        # 情况1:左端点较小
        for l in range(n - 2):
            right_bound = next_greater[l]
            for r in range(l + 2, min(right_bound, n)):
                if nums[r] > nums[l]:
                    result += 1
        
        # 情况2:右端点较小
        for r in range(2, n):
            left_bound = prev_greater[r]
            for l in range(max(0, left_bound + 1), r - 1):
                if nums[l] > nums[r]:
                    result += 1
        
        return result
public class Solution {
    public long BowlSubarrays(int[] nums) {
        int n = nums.Length;
        int[] nextGreater = new int[n];
        int[] prevGreater = new int[n];
        Array.Fill(nextGreater, n);
        Array.Fill(prevGreater, -1);
        
        // 单调栈求右侧第一个更大元素
        Stack<int> stack = new Stack<int>();
        for (int i = 0; i < n; i++) {
            while (stack.Count > 0 && nums[stack.Peek()] < nums[i]) {
                nextGreater[stack.Pop()] = i;
            }
            stack.Push(i);
        }
        
        // 单调栈求左侧第一个更大元素
        stack.Clear();
        for (int i = n - 1; i >= 0; i--) {
            while (stack.Count > 0 && nums[stack.Peek()] < nums[i]) {
                prevGreater[stack.Pop()] = i;
            }
            stack.Push(i);
        }
        
        long result = 0;
        
        // 情况1:左端点较小
        for (int l = 0; l < n - 2; l++) {
            int rightBound = nextGreater[l];
            for (int r = l + 2; r < rightBound && r < n; r++) {
                if (nums[r] > nums[l]) {
                    result++;
                }
            }
        }
        
        // 情况2:右端点较小
        for (int r = 2; r < n; r++) {
            int leftBound = prevGreater[r];
            for (int l = Math.Max(0, leftBound + 1); l <= r - 2; l++) {
                if (nums[l] > nums[r]) {
                    result++;
                }
            }
        }
        
        return result;
    }
}
var bowlSubarrays = function(nums) {
    const n = nums.length;
    const nextGreater = new Array(n).fill(n);
    const prevGreater = new Array(n).fill(-1);
    
    // 单调栈求右侧第一个更大元素
    let stack = [];
    for (let i = 0; i < n; i++) {
        while (stack.length > 0 && nums[stack[stack.length - 1]] < nums[i]) {
            nextGreater[stack.pop()] = i;
        }
        stack.push(i);
    }
    
    // 单调栈求左侧第一个更大元素
    stack = [];
    for (let i = n - 1; i >= 0; i--) {
        while (stack.length > 0 && nums[stack[stack.length - 1]] < nums[i]) {
            prevGreater[stack.pop()] = i;
        }
        stack.push(i);
    }
    
    let result = 0;
    
    // 情况1:左端点较小
    for (let l = 0; l < n - 2; l++) {
        const rightBound = nextGreater[l];
        for (let r = l + 2; r < rightBound && r < n; r++) {
            if (nums[r] > nums[l]) {
                result++;
            }
        }
    }
    
    // 情况2:右端点较小
    for (let r = 2; r < n; r++) {
        const leftBound = prevGreater[r];
        for (let l = Math.max(0, leftBound + 1); l <= r - 2; l++) {
            if (nums[l] > nums[r]) {
                result++;
            }
        }
    }
    
    return result;
};

复杂度分析

复杂度类型
时间复杂度O(n²)
空间复杂度O(n)

注:虽然使用了单调栈优化预处理到O(n),但在枚举有效端点时仍需O(n²)的时间复杂度。空间复杂度为O(n)用于存储单调栈结果。