题目描述

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

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

示例 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

思路:

根据例子可知  解答题意如下
输入:nums = [-2,5,-1], lower = -2, upper = 2
区间和就是从0位置上的数到i位置上面的数
所以【-2,5,-1】其实就是
下面以0结尾的数
0-0    -2
下面是以1结尾的数
0-1   区间和是-2+5=3
1-1   区间和是5
下面是以2为结尾的区间和
0-2  区间和是-2+5±1=2
1-2  区间和是5-1=4
2-2 区间和是-1
所以最后满足题意的[0,0]、[2,2] 和 [0,2] 对应的区间和分别是:-2 、-1 、2 。

本题思路

    *
*
* 首先用到了前缀和数组,前缀和presum[i]表示从0到i数组元素的和
* 之后问题可以转化为:求解(i,j)使得presum(j)-presum(i)∈[lower,upper]
* 因为例如2-5的区间和,就用前缀和来表示就是0到5的累加减去0到2的累加
* 也即前缀和presum(5)-presum(2)
* 所以之后问题可以转化为:求解(i,j)使得presum(j)-presum(i)∈[lower,upper]
* 最直观的解法当然是双指针暴力解,遍历数组来进行逐个查找
*
* 假设0-i整体累加和是x,求必须以i位置结尾的子数组,目标有多少个在【l,up】范围上
* 等同于去求i之前的所有前缀和中有多少个前缀和在【x-up,x-l】上
*
* 题目转化成在前缀和数组中【,,,x,y,z】例如求以x结尾的数落在【L,up]上
* 转出成在x之前有多少数落在【x-up,x-l】
* 就是求x之前的一个指标 在【x-up,x-l】上
*
* 即利用归并排序求解
* i,j在同一边(同在左边或同在右边)
i、j分别在左右数组中
对于第一种情况,可以考虑直接递归求解,
将问题最终递归到左右数组各只有一个元素的情形,结果ret=左递归+右递归
* merge过程因为右组都是减去upper和lower且右组是有序的
* 所以左组中数据是不回退 所以时间复杂度是O(N)的 窗口左边界和右边界只会向右,不会回退
* 这是个规律 可以举例看出来
*

AC代码

import java.util.Scanner;

public class code15_CountOfRangeSum {

//计算区间和的主方法
public static int countRangeSum(int[] nums, int lower, int upper) {
//如果数组为空的话直接返回值为0
if (nums == null || nums.length == 0) {
return 0;
}
//统计前缀和,生成前缀和数组 用long类型,防止溢出
//新建一个数组来存放前缀和
long[] sum = new long[nums.length];
//0——0的前缀和就是0位置上的数
sum[0] = nums[0];
//计算全部的前缀和
for (int i = 1; i < nums.length; i++) {
sum[i] = sum[i - 1] + nums[i];
}
//对前缀和数组,从0开始到最后利用归并排序,在merge的同时结算答案
return process(sum, 0, sum.length - 1, lower, upper);
}
// 返回preSumArr[L...R]上,区间累加和满足条件的个数
public static int process(long[] sum, int L, int R, int lower, int upper) {
//如果左边等于右边表示的是,前缀和中只有一个数
//所以如果这个数满足lower=<数<=upper 则返回1 否则返回0
if (L == R) {
return sum[L] >= lower && sum[L] <= upper ? 1 : 0;
}
//计算中点 >>表示右移一位 表示除以2取整
int M = L + ((R - L) >> 1);
//左区、右区分别递归,结算各自区间上的答案
int left = process(sum, L, M, lower, upper);
int right = process(sum, M + 1, R, lower, upper);
for (int i = 0; i < sum.length; i++) {
System.out.println(sum[i]);
}
//此时左区和右区已经分别有序,左右两区merge,结算左右整体的答案:
int merge = merge(sum, L, M, R, lower, upper);
// System.out.println(left);
// System.out.println(right);
// System.out.println(merge);
return left+right+merge;
}
// preSumArr[L...M] merge preSumArr[M+1...R]
// 左区merge右区,返回符合条件的个数
public static int merge(long[] arr, int L, int M, int R, int lower, int upper) {
int ans = 0;
//左右区互相归并
//定义窗口的左右边界
int windowL = L;
int windowR = L;
// [windowL, windowR)
//遍历右组的数 计算前缀和i位置中有多少值落在 【x-upper----x-lower】之间
for (int i = M + 1; i <= R; i++) {
//当前值-upper作为最小值
//当前值-lower作为最大值
long min = arr[i] - upper;
long max = arr[i] - lower;
//窗口左边界和右边界只会向右
while (windowR <= M && arr[windowR] <= max) {
windowR++;
}
//窗口左边界和右边界只会向右
while (windowL <= M && arr[windowL] < min) {
windowL++;
}
//每一步都累加到ans上面
ans += windowR - windowL;
}
//正常merge为了让其有序
//创建一个新的数组 以下是经典的归并排序的merge的代码
long[] help = new long[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
//归并的过程
while (p1 <= M && p2 <= R) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M) {
help[i++] = arr[p1++];
}
while (p2 <= R) {
help[i++] = arr[p2++];
}
//写回help数组
for (i = 0; i < help.length; i++) {
arr[L + i] = help[i];
}
return ans;
}
//计算区间和
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next().toString();
String[] arr = s.split(",");
int[] nums = new int[arr.length];
for(int j = 0; j<nums.length;j++) {
nums[j] = Integer.parseInt(arr[j]);
// System.out.println(b[j]);
}
int a = sc.nextInt();
int b = sc.nextInt();
int sum = countRangeSum(nums, a, b);
System.out.println(sum);

}
}

新创建一个公众号 Rockey小何同学 想相互交流的同学可以关注一下哈! 感谢支持!