2026-04-03:统计稳定子数组的数目。用go语言,给你一个整数数组 nums。 如果对某个连续子数组 nums[l..r] 来说,它内部不存在“逆序对”,

0 阅读8分钟

2026-04-03:统计稳定子数组的数目。用go语言,给你一个整数数组 nums。

如果对某个连续子数组 nums[l..r] 来说,它内部不存在“逆序对”,也就是没有下标满足 i < j 且 nums[i] > nums[j],那么这个子数组称为“稳定子数组”。(单个元素的子数组也算稳定。)

另外给你一组查询 queries,每个查询是一个区间 [li, ri]。对每个查询,你要统计所有完全落在 nums[li..ri] 范围内的稳定子数组的数量。

请把每个查询的结果按顺序放入数组 ans 中并返回:ans[i] 表示区间 [li, ri] 内稳定子数组的个数。

1 <= nums.length <= 100000。

1 <= nums[i] <= 100000。

1 <= queries.length <= 100000。

queries[i] = [li, ri]。

0 <= li <= ri <= nums.length - 1。

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

输出:[2,3,4]。

解释:

对于 queries[0] = [0, 1],子数组为 [nums[0], nums[1]] = [3, 1]。

稳定子数组包括 [3] 和 [1]。稳定子数组的总数为 2。

对于 queries[1] = [1, 2],子数组为 [nums[1], nums[2]] = [1, 2]。

稳定子数组包括 [1]、[2] 和 [1, 2]。稳定子数组的总数为 3。

对于 queries[2] = [0, 2],子数组为 [nums[0], nums[1], nums[2]] = [3, 1, 2]。

稳定子数组包括 [3]、[1]、[2] 和 [1, 2]。稳定子数组的总数为 4。

因此,ans = [2, 3, 4]。

题目来自力扣3748。

详细步骤解析

一、核心概念理解

  1. 稳定子数组:连续子数组,内部完全递增/非递减(无逆序对),单个元素天然是稳定子数组。
  2. 问题要求:对每个查询区间 [l, r],统计完全落在该区间内的所有稳定子数组的总数。
  3. 数据规模:数组长度和查询数量都达到 10 万级别,必须用O(n) 预处理 + O(1) 单次查询的算法,暴力枚举会超时。

二、算法整体流程(分4大步骤)

步骤1:预处理「递增段」与「前缀和数组」

稳定子数组的本质是最长非递减连续子段的子数组,我们先把原数组拆分成若干个连续的非递减递增段(这是计算的基础)。

  1. 遍历数组,标记以每个位置为右端点的稳定子数组数量

    • 从左到右遍历数组,维护一个计数器 cnt
      • 如果当前元素 ≥ 前一个元素,说明还在同一个递增段内,cnt 加 1;
      • 如果当前元素 < 前一个元素,说明递增段断开,cnt 重置为 1(新段的起点);
    • cnt 的含义:以当前下标为右端点的稳定子数组的个数
    • 示例:nums = [3,1,2]
      • 下标0:3,cnt=1(只有[3])
      • 下标1:1 < 3,cnt=1(只有[1])
      • 下标2:2 ≥ 1,cnt=2([2]、[1,2])
  2. 计算前缀和数组 sum

    • 前缀和数组的作用:快速计算任意区间内所有稳定子数组的总数
    • sum[i] 表示:数组前 i 个元素(下标0~i-1)中,所有稳定子数组的总数。
    • 递推规则:sum[i+1] = sum[i] + cnt(累加每个位置的cnt值)。
    • 示例:sum = [0, 1, 2, 4]

步骤2:预处理「下一个递增段起点」数组 nxt

为了快速判断查询区间是否跨了多个递增段,预处理一个数组 nxt

  • nxt[i] 表示:下标 i 所在递增段的下一个递增段的第一个下标;如果是最后一段,值为数组长度 n
  • 计算方式:从右向左遍历数组
    1. 最后一个元素的 nxt 直接设为数组长度 n
    2. 如果当前元素 ≤ 下一个元素,说明和下一个元素同段,nxt[i] = nxt[i+1]
    3. 如果当前元素 > 下一个元素,说明段在这里断开,nxt[i] = i+1(下一个位置就是新段起点)。
  • 示例:nums = [3,1,2]
    • nxt[2] = 3(数组长度)
    • 1 ≤ 2 → nxt[1] = nxt[2] = 3
    • 3 > 1 → nxt[0] = 1

步骤3:处理每个查询,计算结果

对每个查询 [l, r],分两种情况计算,核心逻辑:查询区间内的稳定子数组,只能是各个递增段内部的子数组(跨段的子数组一定有逆序对,不是稳定子数组)。

情况1:查询区间在同一个递增段内

判断条件:nxt[l] > r(从l出发的下一个段起点,超过了查询右边界r)

  • 计算方式:长度为 m = r-l+1 的连续递增段,稳定子数组总数 = m*(m+1)/2(等差数列求和公式)。
情况2:查询区间跨了多个递增段

判断条件:nxt[l] ≤ r(查询区间被分成两段:[l, nxt[l]-1][nxt[l], r]

  • 第一段:[l, nxt[l]-1] 是完整的递增段,用公式 m*(m+1)/2 计算(m为段长度);
  • 第二段:[nxt[l], r] 直接用前缀和数组快速计算总数(sum[r+1] - sum[nxt[l]]);
  • 总结果 = 第一段数量 + 第二段数量。

步骤4:示例验证(nums=[3,1,2],queries=[[0,1],[1,2],[0,2]])

  1. 查询[0,1]

    • nxt[0]=1 ≤1,跨段
    • 第一段[0,0]:1*2/2=1;第二段[1,1]:sum[2]-sum[1]=1
    • 结果:1+1=2
  2. 查询[1,2]

    • nxt[1]=3>2,同段
    • 长度2:2*3/2=3,结果=3
  3. 查询[0,2]

    • nxt[0]=1 ≤2,跨段
    • 第一段[0,0]:1;第二段[1,2]:sum[3]-sum[1]=3
    • 结果:1+3=4

最终结果:[2,3,4],与题目一致。


三、时间复杂度与额外空间复杂度

1. 总时间复杂度

  • 预处理前缀和数组:O(n)(遍历一次数组);
  • 预处理nxt数组:O(n)(反向遍历一次数组);
  • 处理所有查询:O(q)(q为查询数量,每个查询O(1)计算);
  • 总时间复杂度:O(n + q) 完美适配 10 万级别的数据规模,效率极高。

2. 总额外空间复杂度

  • 前缀和数组 sum:长度 n+1,占用 O(n) 空间;
  • nxt数组:长度 n,占用 O(n) 空间;
  • 答案数组:长度 q,占用 O(q) 空间;
  • 总额外空间复杂度:O(n + q) 除了输入输出外,仅使用了线性空间,无额外冗余空间。

总结

  1. 算法核心:拆分递增段 + 前缀和预处理 + 快速查询,解决大数据量下的暴力超时问题;
  2. 计算逻辑:严格区分查询区间是否跨递增段,分别用公式/前缀和计算;
  3. 效率:时间 O(n+q)、空间 O(n+q),完全满足题目 10 万级数据的要求。

Go完整代码如下:

package main

import (
	"fmt"
)

func countStableSubarrays(nums []int, queries [][]int) []int64 {
	n := len(nums)
	// 计算递增子数组个数的前缀和
	sum := make([]int64, n+1)
	cnt := 0
	for i, x := range nums {
		if i > 0 && x < nums[i-1] {
			cnt = 0
		}
		cnt++
		// 现在 cnt 表示以 i 为右端点的递增子数组个数
		sum[i+1] = sum[i] + int64(cnt)
	}

	// nxt[i] 表示 i 右边下一个递增段的左端点,若不存在则为 n
	nxt := make([]int, n)
	nxt[n-1] = n
	for i := n - 2; i >= 0; i-- {
		if nums[i] <= nums[i+1] {
			nxt[i] = nxt[i+1]
		} else {
			nxt[i] = i + 1
		}
	}

	ans := make([]int64, len(queries))
	for k, q := range queries {
		l, r := q[0], q[1]
		l2 := nxt[l]
		if l2 > r { // l 和 r 在同一个区间
			m := int64(r - l + 1)
			ans[k] = m * (m + 1) / 2
		} else { // l 和 r 在不同区间
			// 分成 [l, l2) + [l2, r]
			// 由于 [l2, r] 中的每个右端点所在递增段的左端点都在 [l2, r] 内,所以可以用前缀和计算
			m := int64(l2 - l)
			ans[k] = m*(m+1)/2 + sum[r+1] - sum[l2]
		}
	}
	return ans
}

func main() {
	nums := []int{3, 1, 2}
	queries := [][]int{{0, 1}, {1, 2}, {0, 2}}
	result := countStableSubarrays(nums, queries)
	fmt.Println(result)
}

在这里插入图片描述

Python完整代码如下:

# -*-coding:utf-8-*-

def count_stable_subarrays(nums, queries):
    n = len(nums)
    # 计算递增子数组个数的前缀和
    sum_prefix = [0] * (n + 1)
    cnt = 0
    for i, x in enumerate(nums):
        if i > 0 and x < nums[i - 1]:
            cnt = 0
        cnt += 1
        # 现在 cnt 表示以 i 为右端点的递增子数组个数
        sum_prefix[i + 1] = sum_prefix[i] + cnt

    # nxt[i] 表示 i 右边下一个递增段的左端点,若不存在则为 n
    nxt = [0] * n
    nxt[n - 1] = n
    for i in range(n - 2, -1, -1):
        if nums[i] <= nums[i + 1]:
            nxt[i] = nxt[i + 1]
        else:
            nxt[i] = i + 1

    ans = []
    for l, r in queries:
        l2 = nxt[l]
        if l2 > r:  # l 和 r 在同一个区间
            m = r - l + 1
            ans.append(m * (m + 1) // 2)
        else:  # l 和 r 在不同区间
            # 分成 [l, l2) + [l2, r]
            # 由于 [l2, r] 中的每个右端点所在递增段的左端点都在 [l2, r] 内,所以可以用前缀和计算
            m = l2 - l
            ans.append(m * (m + 1) // 2 + sum_prefix[r + 1] - sum_prefix[l2])
    return ans


if __name__ == "__main__":
    nums = [3, 1, 2]
    queries = [[0, 1], [1, 2], [0, 2]]
    result = count_stable_subarrays(nums, queries)
    print(result)

在这里插入图片描述

C++完整代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<long long> countStableSubarrays(vector<int>& nums, vector<vector<int>>& queries) {
    int n = nums.size();
    // 计算递增子数组个数的前缀和
    vector<long long> sum(n + 1, 0);
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        if (i > 0 && nums[i] < nums[i - 1]) {
            cnt = 0;
        }
        cnt++;
        // 现在 cnt 表示以 i 为右端点的递增子数组个数
        sum[i + 1] = sum[i] + cnt;
    }

    // nxt[i] 表示 i 右边下一个递增段的左端点,若不存在则为 n
    vector<int> nxt(n, 0);
    nxt[n - 1] = n;
    for (int i = n - 2; i >= 0; i--) {
        if (nums[i] <= nums[i + 1]) {
            nxt[i] = nxt[i + 1];
        } else {
            nxt[i] = i + 1;
        }
    }

    vector<long long> ans(queries.size(), 0);
    for (int k = 0; k < queries.size(); k++) {
        int l = queries[k][0], r = queries[k][1];
        int l2 = nxt[l];
        if (l2 > r) { // l 和 r 在同一个区间
            long long m = r - l + 1;
            ans[k] = m * (m + 1) / 2;
        } else { // l 和 r 在不同区间
            // 分成 [l, l2) + [l2, r]
            // 由于 [l2, r] 中的每个右端点所在递增段的左端点都在 [l2, r] 内,所以可以用前缀和计算
            long long m = l2 - l;
            ans[k] = m * (m + 1) / 2 + sum[r + 1] - sum[l2];
        }
    }
    return ans;
}

int main() {
    vector<int> nums = {3, 1, 2};
    vector<vector<int>> queries = {{0, 1}, {1, 2}, {0, 2}};
    vector<long long> result = countStableSubarrays(nums, queries);

    cout << "[";
    for (int i = 0; i < result.size(); i++) {
        if (i > 0) cout << " ";
        cout << result[i];
    }
    cout << "]" << endl;

    return 0;
}

在这里插入图片描述