📜  门| GATE-CS-2017(Set 2)|问题15(1)

📅  最后修改于: 2023-12-03 15:42:19.043000             🧑  作者: Mango

问题

题目描述

有一个长度为n的数组A[1...n],其每个元素都是一个非负整数。现在,给定另一个整数k,你需要回答有多少个子数组的和不大于k。

输入格式

输入的第一行包含T,表示测试用例的数量。每个测试用例的第一行包含两个整数n和k。第二行包含n个整数A[1]..A[n](每个元素都不大于10000,可能包括0)。

输出格式

对于每个测试用例,输出一个整数,表示和不大于k的子数组数。

输入样例
2
3 1
1 1 1
3 2
1 2 3
输出样例
0
2

解答

题目要求回答的是和不大于k的子数组数,因此首先要将数组所有子数组的和计算出来。接着,将这个子数组和的序列排序,再用类似于双指针的方式计算和不大于k的子数组数。

详细解释见代码注释。

时间复杂度

计算子数组和的时间复杂度为 $O(n^2)$,排序的时间复杂度为 $O(nlogn)$,计算和不大于k的子数组数的时间复杂度为$O(n)$,因此总的时间复杂度为 $O(n^2 + nlogn)$,即 $O(n^2)$

def count_subarrays_with_sum_not_greater_than_k(n, k, A):
    """
    计算和不大于k的子数组数
    :param n: 数组的长度
    :param k: 和不大于k的子数组数
    :param A: 数组A
    :return: 返回子数组的数目
    """
    # 数组的前缀和
    prefix_sum = [A[0]]
    for i in range(1, n):
        prefix_sum.append(prefix_sum[-1] + A[i])

    # 数组的所有子数组的和
    subarray_sums = []
    for i in range(n):
        for j in range(i, n):
            subarray_sums.append(prefix_sum[j] - prefix_sum[i] + A[i])

    # 对子数组和进行排序
    subarray_sums.sort()

    count = 0
    left, right = 0, len(subarray_sums) - 1
    while left <= right:
        mid = (left + right) // 2
        # 如果 mid 的值太大,则将 right 调整到 mid 的左边;
        # 如果 mid 的值太小,则将 left 调整到 mid 的右边;
        # 否则,找到了和不大于k的第一个子数组和,将 left 调整到 mid 的右边,继续向右查找。
        if subarray_sums[mid] > k:
            right = mid - 1
        else:
            count = mid + 1
            left = mid + 1

    return count

代码中主要的时间复杂度在于计算子数组和和排序子数组和这两个步骤。计算子数组和的时间复杂度为 $O(n^2)$,排序的时间复杂度为 $O(nlogn)$,计算和不大于k的子数组数的时间复杂度为$O(n)$,因此总的时间复杂度为 $O(n^2 + nlogn)$,即 $O(n^2)$。