leetcode
leetcode 301 ~ 350
区间和的个数

区间和的个数

难度:

标签:

题目描述

给你一个整数数组 nums 以及两个整数 lowerupper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数

区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (ij)。

 

示例 1:
输入:nums = [-2,5,-1], lower = -2, upper = 2
输出:3
解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。

示例 2:

输入:nums = [0], lower = 0, upper = 0
输出:1

 

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • -105 <= lower <= upper <= 105
  • 题目数据保证答案是一个 32 位 的整数

代码结果

运行时间: 704 ms, 内存: 32.1 MB


/*
  Solution Approach using Java Streams:
  1. Convert the array `nums` into a list of prefix sums.
  2. Use streams to filter and count the number of valid subarrays within the range [lower, upper].
*/
 
import java.util.stream.IntStream;
 
public class Solution {
  public int countRangeSum(int[] nums, int lower, int upper) {
    long[] prefixSum = new long[nums.length + 1];
    for (int i = 0; i < nums.length; i++) {
      prefixSum[i + 1] = prefixSum[i] + nums[i];
    }
 
    return (int) IntStream.range(0, nums.length)
      .flatMap(i -> IntStream.range(i + 1, nums.length + 1)
        .filter(j -> {
          long sum = prefixSum[j] - prefixSum[i];
          return sum >= lower && sum <= upper;
        }))
      .count();
  }
}

解释

方法:

该题解采用分治法与归并排序的思想来解决问题。首先,我们计算了数组的前缀和,然后通过递归分治方法处理每个子数组,并在合并的过程中统计符合条件的区间和的个数。具体步骤如下: 1. 计算数组的前缀和,以便快速计算任意子数组的和。 2. 使用递归分治的方法来处理和合并子数组。对于每个子问题,如果它只包含一个元素,直接判断这个单独元素形成的区间和是否在指定范围内。 3. 在合并两个已经排序的子数组的过程中,使用双指针技术统计当前右侧元素与左侧元素组合形成的区间和落在[lower, upper]范围内的个数。 4. 合并完成后,还需要将左右两部分归并排序,以便后续操作可以继续利用已排序的性质加速区间和的计算。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在计算区间和的个数时选择使用前缀和而不是直接遍历所有可能的区间组合?
使用前缀和可以将计算任意子数组的和的时间复杂度从线性时间降低到常数时间。如果直接遍历所有区间组合来计算区间和,则对于长度为 n 的数组,可能的区间组合数为 n*(n+1)/2,这将导致时间复杂度为 O(n^2),这在 n 较大时效率非常低。通过计算前缀和数组,我们只需 O(n) 时间就可以得到整个数组的前缀和,之后计算任何一个区间和都可以在 O(1) 时间内完成,从而大大提高效率。
🦆
分治法在这个问题中具体是如何实现的?能否详细解释其在合并步骤中的作用和机制?
在这个问题中,分治法通过递归地将数组分成更小的子数组,直到子数组只包含一个元素,然后在合并过程中计算跨越两个子数组的区间和是否在给定的范围内。具体来说,我们首先计算左半部分和右半部分内部各自满足条件的区间和的数量,然后计算跨越左右子数组的区间和的数量。在合并步骤中,我们使用双指针技术:一个指针遍历右子数组,另外两个指针在左子数组中移动,定位符合条件的区间和范围。这种方法利用了区间和的性质,即如果我们知道右子数组的某个前缀和,可以快速通过左子数组的前缀和确定哪些和落在指定范围内。这种分治加合并的策略有效地减少了重复计算,从而提高了算法的效率。
🦆
在合并过程中使用双指针统计符合条件的区间和的数量,这种方法的时间复杂度是多少?
在合并过程中,使用双指针来统计符合条件的区间和的数量主要涉及遍历右子数组的每个元素,并对每个元素使用两个指针在左子数组中寻找符合条件的前缀和范围。这个过程的时间复杂度是 O(n),因为虽然对每个右子数组元素都可能进行一系列比较,但是两个指针(window_l 和 window_r)都是单向移动,即每个指针在整个合并过程中最多移动 n 步。因此,这一步的总体操作数是线性的,即 O(n)。这比直接计算每个可能的区间和要高效得多。

相关问题

计算右侧小于当前元素的个数

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

 

示例 1:

输入:nums = [5,2,6,1]
输出:[2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

示例 2:

输入:nums = [-1]
输出:[0]

示例 3:

输入:nums = [-1,-1]
输出:[0,0]

 

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

翻转对

给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对

你需要返回给定数组中的重要翻转对的数量。

示例 1:

输入: [1,3,2,3,1]
输出: 2

示例 2:

输入: [2,4,3,5,1]
输出: 3

注意:

  1. 给定数组的长度不会超过50000
  2. 输入数组中的所有数字都在32位整数的表示范围内。