2026-06-08:开销小于等于 K 的子数组数目。用go语言,给定整数数组 nums 和整数 k。 对数组中任意一个连续非空子数组 nums[l…..

网易专栏15小时前发布 nxnqh
1 0 0

🤖 AI总结

主题

使用滑动窗口和单调队列求解代价不超过K的子数组数目

摘要

通过滑动窗口和两个单调队列维护最大值最小值,线性时间内统计代价不超过K的子数组数目,并提供Go/Python/C++代码实现。

关键信息

  • 1 算法题来自力扣3835
  • 2 使用双端队列维护窗口最值
  • 3 滑动窗口+单调队列实现O(n)解法

2026-06-08:开销小于等于 K 的子数组数目。用go语言,给定整数数组 nums 和整数 k。
对数组中任意一个连续非空子数组 nums[l..r],先找出该子数组的最大值 max 和最小值 min。然后用它们的差值乘以子数组长度 (r – l + 1),得到该子数组的“代价”。

题目要求:统计 nums 里所有子数组中,代价不超过 k 的子数组一共有多少个,并返回这个数量。

1 <= nums.length <= 100000。

1 <= nums[i] <= 1000000000。

0 <= k <= 1000000000000000。

输入: nums = [1,3,2], k = 4。

输出: 5。

解释:

考虑 nums 的所有子数组:

nums[0..0]: cost = (1 – 1) * 1 = 0

nums[0..1]: cost = (3 – 1) * 2 = 4

nums[0..2]: cost = (3 – 1) * 3 = 6

nums[1..1]: cost = (3 – 3) * 1 = 0

nums[1..2]: cost = (3 – 2) * 2 = 2

nums[2..2]: cost = (2 – 2) * 1 = 0

共有 5 个子数组的开销小于或等于 4。

题目来自力扣3835。

逐步骤详细执行过程

初始状态:

• 左指针left = 0

  • • 最小队列minQ空,最大队列maxQ

  • • 答案ans = 0

  • • 数组:[1, 3, 2],索引0、1、2

    第一步:遍历右边界 right = 0,元素值 = 1 1. 元素入队(维护两个单调队列)

    • 最小队列:空,直接把下标0加入 →minQ = [0](队首是当前最小值)

  • • 最大队列:空,直接把下标0加入 →maxQ = [0](队首是当前最大值)

    2. 检查窗口是否合法(代价 ≤k)

    当前窗口[0,0]
    最大值=1,最小值=1,差值=0
    长度=1
    代价=0×1=0 ≤4 → 窗口合法,不移动左指针

    3. 统计合法子数组数量

    right=0结尾的合法子数组:只有[0,0]
    答案累加:ans = 0 + 1 = 1

    第二步:遍历右边界 right = 1,元素值 = 3 1. 元素入队(维护两个单调队列)

    • 最小队列:当前元素3 ≥ 队尾下标0对应的值1,不破坏单调递增,直接加入 →minQ = [0,1]

  • • 最大队列:当前元素3 ≥ 队尾下标0对应的值1,需要弹出队尾,队列变空后加入 →maxQ = [1]

    2. 检查窗口是否合法

    当前窗口[0,1]
    最大值=3(队首1),最小值=1(队首0),差值=2
    长度=2
    代价=2×2=4 ≤4 → 窗口合法,不移动左指针

    3. 统计合法子数组数量

    right=1结尾的合法子数组:[0,1][1,1]→ 共2个
    答案累加:ans = 1 + 2 = 3

    第三步:遍历右边界 right = 2,元素值 = 2 1. 元素入队(维护两个单调队列)

    • 最小队列:当前元素2 ≤ 队尾下标1对应的值3,弹出队尾;队列变为[0],2 ≥ 下标0对应的值1,加入 →minQ = [0,2]

  • • 最大队列:当前元素2 ≤ 队尾下标1对应的值3,不破坏单调递减,直接加入 →maxQ = [1,2]

    2. 检查窗口是否合法(关键:窗口不合法,移动左指针)

    当前窗口[0,2]
    最大值=3(队首1),最小值=1(队首0),差值=2
    长度=3
    代价=2×3=6 >4 →窗口不合法

    • 左指针右移:left = 1

  • • 检查最小队列:队首0 < left=1,弹出 →minQ = [2]

  • • 检查最大队列:队首1 ≥ left=1,保留

    现在窗口[1,2]
    最大值=3(队首1),最小值=2(队首2),差值=1
    长度=2
    代价=1×2=2 ≤4 → 窗口合法,停止移动左指针

    3. 统计合法子数组数量

    right=2结尾的合法子数组:[1,2][2,2]→ 共2个
    答案累加:ans = 3 + 2 = 5

    遍历结束

    最终总答案 = 5,和题目输出完全一致。

    时间复杂度 & 额外空间复杂度 1. 总时间复杂度:O(n)

    • 滑动窗口:左右指针leftright只会向右移动,绝不回退,总共最多移动2n次;

  • • 单调队列:每个数组元素只会入队一次、出队一次,没有重复操作;

  • • 所有操作都是线性的,与数组长度n成正比。

    2. 总额外空间复杂度:O(n)

    • 额外使用了两个单调队列存储数组下标;

  • • 最坏情况下(数组严格递增/递减),队列会存储全部n个元素;

  • • 除了输入输出和变量,仅占用线性级别的额外空间。

    总结

    1. 执行过程:逐一遍历右边界→维护双队列获取窗口最值→检查窗口合法性→移动左指针→统计合法子数组数量并累加;

  • 2. 时间复杂度:O(n)(线性时间,适合10万长度的数组);

  • 3. 额外空间复杂度:O(n)(两个单调队列的最大存储量)。

    Go完整代码如下:

    package main

    import (
    "fmt"
    )

    func countSubarrays(nums []int, k int64) (ans int64) {
    var minQ, maxQ []int
    left := 0
    for right, x := range nums {
    // 1. 入:元素进入窗口
    forlen(minQ) > 0 && x <= nums[minQ[len(minQ)-1]] {
    minQ = minQ[:len(minQ)-1]
    }
    minQ = append(minQ, right)

    forlen(maxQ) > 0 && x >= nums[maxQ[len(maxQ)-1]] {
    maxQ = maxQ[:len(maxQ)-1]
    }
    maxQ = append(maxQ, right)

    // 2. 出:如果窗口不满足要求,左端点离开窗口
    // 只需改下面这行代码,其余逻辑和 2762 题完全一致
    forint64(nums[maxQ[0]]-nums[minQ[0]])*int64(right-left+1) > k {
    left++
    if minQ[0] < left {
    minQ = minQ[1:]
    }
    if maxQ[0] < left {
    maxQ = maxQ[1:]
    }
    }

    // 3. 更新答案
    ans += int64(right - left + 1)
    }
    return
    }

    func main() {
    nums := []int{1, 3, 2}
    k := 4
    result := countSubarrays(nums, int64(k))
    fmt.Println(result)
    }

    2026-06-08:开销小于等于 K 的子数组数目。用go语言,给定整数数组 nums 和整数 k。 对数组中任意一个连续非空子数组 nums[l.....

    Python完整代码如下:

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

    from collections import deque

    def countSubarrays(nums, k):
    ans = 0
    minQ = deque()
    maxQ = deque()
    left = 0
    for right, x in enumerate(nums):
    # 1. 入:元素进入窗口
    while minQ and x <= nums[minQ[-1]]:
    minQ.pop()
    minQ.append(right)
    while maxQ and x >= nums[maxQ[-1]]:
    maxQ.pop()
    maxQ.append(right)
    # 2. 出:如果窗口不满足要求,左端点离开窗口
    while maxQ and minQ and (nums[maxQ[0]] - nums[minQ[0]]) * (right - left + 1) > k:
    left += 1
    if minQ and minQ[0] < left:
    minQ.popleft()
    if maxQ and maxQ[0] < left:
    maxQ.popleft()
    # 3. 更新答案
    ans += right - left + 1
    return ans

    def main():
    nums = [1, 3, 2]
    k = 4
    result = countSubarrays(nums, k)
    print(result)

    if __name__ == "__main__":
    main()

    2026-06-08:开销小于等于 K 的子数组数目。用go语言,给定整数数组 nums 和整数 k。 对数组中任意一个连续非空子数组 nums[l.....

    C++完整代码如下:

      
    



    using namespace std;

    long long countSubarrays(vector& nums, long long k) {
    long long ans = 0;
    deque minQ, maxQ;
    int left = 0;

    for (int right = 0; right < nums.size(); right++) {
    int x = nums[right];

    // 1. 入:元素进入窗口
    while (!minQ.empty() && x <= nums[minQ.back()]) {
    minQ.pop_back();
    }
    minQ.push_back(right);

    while (!maxQ.empty() && x >= nums[maxQ.back()]) {
    maxQ.pop_back();
    }
    maxQ.push_back(right);

    // 2. 出:如果窗口不满足要求,左端点离开窗口
    while (!minQ.empty() && !maxQ.empty() &&
    (long long)(nums[maxQ.front()] - nums[minQ.front()]) * (right - left + 1) > k) {
    left++;
    if (!minQ.empty() && minQ.front() < left) {
    minQ.pop_front();
    }
    if (!maxQ.empty() && maxQ.front() < left) {
    maxQ.pop_front();
    }
    }

    // 3. 更新答案
    ans += (right - left + 1);
    }

    return ans;
    }

    int main() {
    vector nums = {1, 3, 2};
    long long k = 4;
    long long result = countSubarrays(nums, k);
    cout << result << endl;
    return0;
    }

    2026-06-08:开销小于等于 K 的子数组数目。用go语言,给定整数数组 nums 和整数 k。 对数组中任意一个连续非空子数组 nums[l.....

    我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

    © 版权声明

    相关文章