【刷题日记】2104. 子数组范围和

1,115 阅读5分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

2104. 子数组范围和

对于很久没有刷题的我来说,突然开始也开始刷题了,不为别的,已经习惯参与掘金的活动了,同时也可以促进自己再回顾一下算法题,毕竟长时间不练,真的就生疏了

本月开始刷题了,尝试写题解,希望能够对你有帮助

一、题目描述:

给你一个整数数组 nums 。nums 中,子数组的 范围 是子数组中最大元素和最小元素的差值。

返回 nums 中 所有 子数组范围的 和 。

子数组是数组中一个连续 非空 的元素序列

image-20220305170736018

二、思路分析:

1、这道题考察了什么思想?你的思路是什么?

看了这题的描述和示例,我们应该得到了如下几个信息

  • 从给出的数组中,要找到所有的子数组,子数组中的元素顺序是要和原有数组元素顺序是保持一致的
  • 对于每一个子数组,我们需要找到该子数组中的最大值,和最小值并计算差值
  • 需要将所有子数组的计算的结果,求和

对于这个子数组,需要注意的地方是,要是连续的,非空的

如果我们认为可以向上述这样组成子数组那么思考方向就是错误的,这里一定需要注意

根据以上已知信息,我们可以一步一步的来推导,先看如何找到子数组

由于是需要在子数组中找到最大数和最小数的差值,那么子数组中只有一个元素的情况,我们就直接忽略,首先根据题意,子数组要非空,那么若子数组只有 1 个元素,则最大数和最小数的差值是 0

我们可以用上述示例 3 来进行演示:

4 , -2 ,-3,4, 1

根据上图,我们不难理解,

  • 就是先指定左边第 1 个元素为起始位置,然后不停的向右扩,扩充到的每一个子数组,计算最大值和最小值的差值,直到扩充到数组长度后
  • 再从左边起始的第 2 个元素,再继续向右扩
  • 最终,当起始元素指到数组的最右边的一个元素的时候,则查找完毕
  • 最终将所有的子数组的差值求和

2、尝试编码

那么,根据上述的图示和逻辑,咱们不难写出类似于这样的代码

这样的实现方式,对于大多数用例也是可以满足的,但是对于数量大一点的用例,实际情况是通过了 56 个用例但是对于数据复杂一点的用例就会出现时间超限的情况

仔细查看咱们写的代码,实际上是使用了 3 层 for 循环 ,这在生产环境中肯定是不允许的

因此,咱们一定有优化的空间

3、思考和探索

上述对于计算每一个子数组的最大值和最小值的做法是一样的,筛选出的子数组的方式也是一样的,那么,我们是否可以换一种思路来看看这个题,想办法筛选出子数组的 max ,和 min 分别对应放置,最终,直接计算对应的数据差值,再求和即可,这样就可以杜绝 3 个 for 循环了

例如上图,我们第一步,以及将 [4,-2] 最大值 和 最小值计算出来,那么 [4,-2,-3] 子数组的时候,只需要和 [4,-2] 的结果进行比较和计算即可

例如计算 [4,-2,-3] 最大值的时候,只需要 将 [4,-2] 子数组中的最大值 和 -3 进行比较,那么一定是 [4,-2,-3] 的最大值

最小值的计算逻辑也是一样,其他的子数组计算逻辑也是保持一致

那么,我们可以推算出如下这样的表格

按照上述的方式,将子数组的 max 放到 big 表格中,min 放到 small 表格中

[4,-2],计算出 max 为 4 放到 big 表格中,min 为 -2 放到 small 表格中

[4,-2,-3],计算出 max 为 4 放到 big 表格中,min 为 -3 放到 small 表格中

同理可以得出上述第一行的最大值和最小值, 其余行依次类推,最终得出如下表格:

源码如下:

func subArrayRanges(nums []int) int64 {
	length := len(nums)
	big := make([][]int, length)
	small := make([][]int, length)
	// small 表格 和 big 表格 ,对角线是 nums 数组元素
	for i := 0; i < length; i++ {
		big[i] = make([]int, length)
		big[i][i] = nums[i]
		small[i] = make([]int, length)
		small[i][i] = nums[i]
	}
	// 分别绘制 small 表格,和 big 表格
	for i := 1; i < length; i++ {
		for j := 0; j+i < length; j++ {
			big[j][j+i] = max(big[j][j+i-1], nums[j+i])
			small[j][j+i] = min(small[j][j+i-1], nums[j+i])
		}
	}
	var sum int
	for i := 0; i < length; i++ {
		for j := i + 1; j < length; j++ {
			sum += big[i][j] - small[i][j]
		}
	}
	return int64(sum)
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

当然,这样显的代码行数有点多,但是没有关系,咱们仔细思考一下,就不难理解官方的题解了

原理和我们上述推理过程一致,按照顺序找到子数组,后一个数组的计算参数,依赖于上一个子数组的结果

四、总结:

本题考察对应数组的应用,遍历子数组并进行相应的计算,这种方式时间复杂度是 O(n^2) , 空间复杂度是 O(1)

今天就到这里,学习所得,若有偏差,还请斧正

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

好了,本次就到这里

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~