【好记性不如烂笔头】排序算法之归并排序(四)力扣困难难度:区间和的个数


前言

  学习了归并排序,也要学习使用归并排序解决问题,先有力扣的一个困难难度的题327. 区间和的个数


题意解析

  从小阅读理解也没得过高分,不过还是要说一下自己的理解。

原题描述

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

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

解析题意

  假定一个数组:

在这里插入图片描述

  将这个数组拆分成各个小数组,小数组的累加和,满足 [lower, upper]的累加和的个数,就是最终要的结果。

在这里插入图片描述


明晰思路

发现规律

  暴力解析是个笨办法,但是往往这些笨办法中蕴含者一些规律:

  • 拆分的数组个数,越来越少,第一次4个,第二次3个,第三次2个,第四次1个
  • 以开头统计达标,和以结尾统计达标一样,上面是以开头统计,0–0,0–1,0–2,0–3,也可以以结尾统计,0–3,1–3,2–3,3–3.

  这个规律有啥用,以开头统计的话,我不知道后面的累加上是否满足规则,以结尾统计的话,我用0–upper的累加和减去0–lower的累加和,不久得到lower-upper的累加和了?我这里提到的累加和不是真的是一个和,算法上叫前缀和,啥,咋又多出来个前缀和?这里简单解释一下吧,来个图解

在这里插入图片描述

  回到原题,对这个数组求满足 [5, 10]的累加和的个数,先看下以7结尾的满足[5,10]的有哪些,

  • 下标==》0到7 减去 0到0 得到 1到7 :21-1=20
  • 下标==》0到7 减去 0到1 得到 2到7 :21-3=20
  • 下标==》0到7 减去 0到2 得到 3到7 :21-0=21
  • 下标==》0到7 减去 0到3 得到 4到7 :21-5=16
  • 下标==》0到7 减去 0到4 得到 5到7 :21-14=7
  • 下标==》0到7 减去 0到5 得到 6到7 :21-14=7
  • 下标==》0到7 减去 0到6 得到 7到7 :21-18=3
  • 5到7 6到7 满足[5,10]

  又有个规律出现了,以7结尾的数据的累加和是21,需要满足的累加和规则是[5,10],那我前缀和满足[21-10,21-5]==>[11,16],就得到满足[5,10]的统计了呗,图解如下:

在这里插入图片描述

代码思路

  依赖前缀和实现,所以先搞一个方法实现前缀和,当前位置的前缀和就是上一位置的前缀和+这个位置的值

sum[i] = sum[i-1] + nums[i] 循环实现搞到前缀和数组sum。

  我需要拆分数组啊,咋搞,之前学了拆分用递归,splitSorting(int[] arr, int l, int r)

本次还有规则范围判断呢,把规则范围也加进去,splitSorting(int[] arr, int l, int r,int lower,int upper)

递归完成的内容就是判断,子数组有多少个达标的。

伪代码:

splitSorting(int[] arr, int l, int r,int lower,int upper){
	//考虑边界,弹出递归
	if(l==r){
		//装边界了,看看这个位置的前缀和是否在范围内,如果在计数1,否则不计数。
		return arr[l]>l && arr[r] <r ? 1: 0;
	}
	int m = (l+r)/2;
	int zuo = splitSorting(arr,l,m,lower,upper);
	int you = splitSorting(arr,m+1,r,lower,upper);
	//还需要判断是否达标的方法
	int mer = merge(……);
	return zuo + you + mer;
}

  可是这也和上次的归并排序关系不大呀,归并排序主要采用分治排序的思路,怎么才能用上呢?

大神就是不一样,这是怎么想到的,merge方法将顺序排好了,那就意味着每个数的范围按照规则[当前和-10,当前和-5],必定是有序的😮
在这里插入图片描述

  🤔,是升序的又有啥用呢?左组中的数在右组的新的范围中的数有几个。

代码实现

	public static int countRangeSum(int[] nums, int lower, int upper) {
		if (nums == null || nums.length == 0) {
			return 0;
		}
		//前缀和
		long[] sum = new long[nums.length];
		//0--0前缀和就是自己
		sum[0] = nums[0];
		for (int i = 1; i < nums.length; i++) {
			//上一次的累加和+本次的数,就是本次的累加和,放在i位置,作为i位置的前缀和
			sum[i] = sum[i - 1] + nums[i];
		}
		return process(sum, 0, sum.length - 1, lower, upper);
	}

	public static int process(long[] sum, int L, int R, int lower, int upper) {
		if (L == R) {
			return sum[L] >= lower && sum[L] <= upper ? 1 : 0;
		}
		int M = L + ((R - L) >> 1);
		return process(sum, L, M, lower, upper) + process(sum, M + 1, R, lower, upper)
				+ merge(sum, L, M, R, lower, upper);
	}

	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)
		for (int i = M + 1; i <= R; i++) {
			long min = arr[i] - upper;
			long max = arr[i] - lower;
			//以这个结尾的前缀和的规则变成了[min,max]
			//左组中有多少个数满足这个新规则
			while (windowR <= M && arr[windowR] <= max) {
				windowR++;
			}
			while (windowL <= M && arr[windowL] < min) {
				windowL++;
			}
			ans += windowR - windowL;
		}
		
		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++];
		}
		for (i = 0; i < help.length; i++) {
			arr[L + i] = help[i];
		}
		return ans;
	}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泪梦殇雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值