Hard

题目描述

给你一个整数数组 nums

如果 nums 的一个子数组不包含逆序对,即不存在下标对 i < j 使得 nums[i] > nums[j],则称该子数组是稳定的

同时给你一个长度为 q 的二维整数数组 queries,其中 queries[i] = [li, ri] 表示一个查询。对于每个查询 [li, ri],计算完全位于区间 nums[li..ri] 内的稳定子数组的数量。

返回一个长度为 q 的整数数组 ans,其中 ans[i] 是第 i 个查询的答案。

注意:

  • 单个元素的子数组被认为是稳定的。

示例 1:

输入:nums = [3,1,2], queries = [[0,1],[1,2],[0,2]]
输出:[2,3,4]
解释:
- 对于 queries[0] = [0, 1],子数组是 [3, 1]。
  稳定的子数组有 [3] 和 [1],总数为 2。
- 对于 queries[1] = [1, 2],子数组是 [1, 2]。
  稳定的子数组有 [1]、[2] 和 [1, 2],总数为 3。
- 对于 queries[2] = [0, 2],子数组是 [3, 1, 2]。
  稳定的子数组有 [3]、[1]、[2] 和 [1, 2],总数为 4。

示例 2:

输入:nums = [2,2], queries = [[0,1],[0,0]]
输出:[3,1]

约束条件:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5
  • 1 <= queries.length <= 10^5
  • queries[i] = [li, ri]
  • 0 <= li <= ri <= nums.length - 1

解题思路

解题思路

核心观察: 稳定子数组就是非递减子数组,即不存在逆序对的子数组。

关键洞察:

  1. 我们需要找到所有最大的非递减段,每个长度为 L 的段贡献 L * (L + 1) / 2 个稳定子数组
  2. 对于查询 [l, r],我们需要快速计算该区间内的稳定子数组数量

算法步骤:

  1. 预处理阶段:

    • 找到每个位置能向左扩展的最大非递减段长度
    • 构建前缀和数组,记录以每个位置结尾的稳定子数组总数
  2. 查询处理:

    • 使用前缀和快速计算区间内的稳定子数组数量
    • 需要特别处理跨越查询左边界的子数组

具体实现:

  • dp[i] 表示以位置 i 结尾的稳定子数组数量
  • prefix[i] 表示前 i+1 个位置的稳定子数组总数
  • 对于查询 [l, r],答案是 prefix[r] - prefix[l-1] 再减去跨越左边界的多余部分

时间复杂度为 O(n + q),空间复杂度为 O(n),能够高效处理大规模数据。

代码实现

class Solution {
public:
    vector<long long> countStableSubarrays(vector<int>& nums, vector<vector<int>>& queries) {
        int n = nums.size();
        vector<long long> dp(n), prefix(n);
        
        // dp[i] = number of stable subarrays ending at i
        dp[0] = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] >= nums[i-1]) {
                dp[i] = dp[i-1] + 1;
            } else {
                dp[i] = 1;
            }
        }
        
        // prefix[i] = total stable subarrays in [0, i]
        prefix[0] = dp[0];
        for (int i = 1; i < n; i++) {
            prefix[i] = prefix[i-1] + dp[i];
        }
        
        vector<long long> result;
        for (auto& query : queries) {
            int l = query[0], r = query[1];
            long long total = prefix[r] - (l > 0 ? prefix[l-1] : 0);
            
            // Subtract overcounted subarrays that start before l but end at or after l
            if (l > 0 && nums[l] >= nums[l-1]) {
                long long len = 1;
                int j = l - 1;
                while (j > 0 && nums[j] >= nums[j-1]) {
                    len++;
                    j--;
                }
                total -= len * (len + 1) / 2 - dp[l-1];
            }
            
            result.push_back(total);
        }
        
        return result;
    }
};
class Solution:
    def countStableSubarrays(self, nums: List[int], queries: List[List[int]]) -> List[int]:
        n = len(nums)
        dp = [0] * n
        prefix = [0] * n
        
        # dp[i] = number of stable subarrays ending at i
        dp[0] = 1
        for i in range(1, n):
            if nums[i] >= nums[i-1]:
                dp[i] = dp[i-1] + 1
            else:
                dp[i] = 1
        
        # prefix[i] = total stable subarrays in [0, i]
        prefix[0] = dp[0]
        for i in range(1, n):
            prefix[i] = prefix[i-1] + dp[i]
        
        result = []
        for l, r in queries:
            total = prefix[r] - (prefix[l-1] if l > 0 else 0)
            
            # Subtract overcounted subarrays that start before l but end at or after l
            if l > 0 and nums[l] >= nums[l-1]:
                length = 1
                j = l - 1
                while j > 0 and nums[j] >= nums[j-1]:
                    length += 1
                    j -= 1
                total -= length * (length + 1) // 2 - dp[l-1]
            
            result.append(total)
        
        return result
public class Solution {
    public long[] CountStableSubarrays(int[] nums, int[][] queries) {
        int n = nums.Length;
        long[] dp = new long[n];
        long[] prefix = new long[n];
        
        // dp[i] = number of stable subarrays ending at i
        dp[0] = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] >= nums[i-1]) {
                dp[i] = dp[i-1] + 1;
            } else {
                dp[i] = 1;
            }
        }
        
        // prefix[i] = total stable subarrays in [0, i]
        prefix[0] = dp[0];
        for (int i = 1; i < n; i++) {
            prefix[i] = prefix[i-1] + dp[i];
        }
        
        long[] result = new long[queries.Length];
        for (int q = 0; q < queries.Length; q++) {
            int l = queries[q][0], r = queries[q][1];
            long total = prefix[r] - (l > 0 ? prefix[l-1] : 0);
            
            // Subtract overcounted subarrays that start before l but end at or after l
            if (l > 0 && nums[l] >= nums[l-1]) {
                long length = 1;
                int j = l - 1;
                while (j > 0 && nums[j] >= nums[j-1]) {
                    length++;
                    j--;
                }
                total -= length * (length + 1) / 2 - dp[l-1];
            }
            
            result[q] = total;
        }
        
        return result;
    }
}
var countStableSubarrays = function(nums, queries) {
    const n = nums.length;
    const dp = new Array(n);
    const prefix = new Array(n);
    
    // dp[i] = number of stable subarrays ending at i
    dp[0] = 1;
    for (let i = 1; i < n; i++) {
        if (nums[i] >= nums[i-1]) {
            dp[i] = dp[i-1] + 1;
        } else {
            dp[i] = 1;
        }
    }
    
    // prefix[i] = total stable subarrays in [0, i]
    prefix[0] = dp[0];
    for (let i = 1; i < n; i++) {
        prefix[i] = prefix[i-1] + dp[i];
    }
    
    const result = [];
    for (const [l, r] of queries) {
        let total = prefix[r] - (l > 0 ? prefix[l-1] : 0);
        
        // Subtract overcounted subarrays that start before l but end at or after l
        if (l > 0 && nums[l] >= nums[l-1]) {
            let length = 1;
            let j = l - 1;
            while (j > 0 && nums[j] >= nums[j-1]) {
                length++;
                j--;
            }
            total -= Math.floor(length * (length + 1) / 2) - dp[l-1];
        }
        
        result.push(total);
    }
    
    return result;
};

复杂度分析

复杂度大小
时间复杂度O(n + q)
空间复杂度O(n)

其中 n 是数组长度,q 是查询数量。预处理需要 O(n) 时间,每个查询需要 O(1) 时间处理。