前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >leetcode 907子数组的最小值之和题解

leetcode 907子数组的最小值之和题解

作者头像
ACK
发布2020-01-14 18:06:06
1.4K0
发布2020-01-14 18:06:06
举报
文章被收录于专栏:flytam之深入前端技术栈
leetcode907 子数组的最小值之和

一道涉及到单调栈的应用的题目

题目如下

代码语言:javascript
复制
给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。
由于答案可能很大,因此返回答案模 10^9 + 7。
代码语言:javascript
复制
输入:[3,1,2,4]
输出:17
解释:
子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。
最小值为 3,1,2,4,1,1,2,1,1,1,和为 17

思路分析:这里是求出子数组的最小值之和,其实并不需要知道这个子数组的除了最大值之外其它数值。通过对实例的分析,我们可以得出一个17运用公式如何算出来:(1+0-0 (0-0)*(0-0))*3+(3-0+1+(3-1)*(1-0))*1+(1+3-2+(3-2)*(2-2))*2+(1+3-3+(3-3)*(3-3))*4 = 17

也就是说,遍历数组的每一个值,找出以该数组为最小值的组合次数,乘积求和为和即可。假设当前数字下标为a,该数字往前的第一个小于该数的下标为x(也就是最小数组最大边界)、往后第一个小于等于该数的下标为y,那么 次数就是y-x+1+(y-a)*(y-b)。例如以[3,1,2,4]2为例子,则a=2 x=2 y=3,所以次数3-2+1+(3-2)*(2-2) = 2

所以这个题目就变成了,找出对于数组中每一个值,它的前继小于自己的下标/后继小于等于自己的下标。那么如何寻找呢。

解法 1: 常规解法。就是遍历数组,每个数字往前往后继续找边界。这样时间复杂度为o(n)。在第 87 个 case 就超时了。

代码语言:javascript
复制
func sumSubarrayMins(A []int) int {
	sum := 0
	length := len(A)
	for i, v := range A {
		tmpLeft, tmpRight := i, i
		for {
			// 都 到达边界或者找到第一个小于等于的
			if tmpLeft-1 >= 0 && A[tmpLeft-1] > v {
				tmpLeft--
			}

			if tmpRight+1 < length && A[tmpRight+1] >= v {
				tmpRight++
			}

			if (tmpLeft-1 < 0 || A[tmpLeft-1] <= v) && (tmpRight+1 == length || A[tmpRight+1] < v) {
				fmt.Println(tmpLeft, tmpRight)
				sum = sum + (i-tmpLeft+tmpRight-i+(tmpRight-i)*(i-tmpLeft)+1)*v
				break
			}
		}
	}
	return sum % int(math.Pow(10, 9)+7)
}

解法 2:稍微优化的版本,记录前一次的边界值,当前值小于前面值,直接从前面的边界开始找。到第 100 个测试样例还是没通过。仔细观察,其实优化版本只对部分 case 简化,其实它还是o(n2)的时间复杂度。要通过这里肯定不能o(n2)

代码语言:javascript
复制
func sumSubarrayMins(A []int) int {
	// 记录前一个的左右边界
	pre := [2]int{}
	sum := 0
	length := len(A)
	var tmpLeft, tmpRight int
	for i, v := range A {
		if i > 0 && v <= A[i-1] {
			if v < A[i-1] {
				// 小于q前面的,左右用前面的
				tmpLeft, tmpRight = pre[0], pre[1]
			} else {
				// 等于,左用自己
				tmpLeft, tmpRight = i, pre[1]
			}

		} else {
			tmpLeft, tmpRight = i, i
		}

		for {
			if tmpLeft-1 >= 0 && A[tmpLeft-1] > v {
				tmpLeft--
			}
			if tmpRight+1 < length && A[tmpRight+1] >= v {
				tmpRight++
			}
			if (tmpLeft-1 < 0 || A[tmpLeft-1] <= v) && (tmpRight+1 == length || A[tmpRight+1] < v) {

				sum = sum + (i-tmpLeft+tmpRight-i+(tmpRight-i)*(i-tmpLeft)+1)*v
				pre[0] = tmpLeft
				pre[1] = tmpRight

				break
			}

		}
	}

	return sum % int(math.Pow(10, 9)+7)
}

解法 3: 使用单调栈找出每一个数的边界。只需要o(n)的时间复杂度就可以找出来(因为每个元素只会进/出栈一次)

代码语言:javascript
复制
type Item struct {
	low  int
	high int
}

func sumSubarrayMins(A []int) int {
	sum := 0
	length := len(A)
	if length == 1 {
		return A[0]
    }
    // lower  每个元素往左查找
    // higher 每个元素往右查找
	lower := []int{length - 1}

	higher := []int{0}

	store := make([]Item, len(A))

	for i := 1; i < length; i++ {

		highIndex := i
		lowIndex := length - i - 1

		// 当前元素小于等于栈顶元素。栈顶元素出栈。
		for len(higher) >= 1 && A[highIndex] <= A[higher[len(higher)-1]] {
			// 存储下标
			store[higher[len(higher)-1]].high = highIndex - 1
			// 出栈
			higher = higher[:len(higher)-1]
		}
		higher = append(higher, highIndex)

		// 当前元素小于栈顶元素。栈顶元素出栈。
		for len(lower) >= 1 && A[lowIndex] < A[lower[len(lower)-1]] {
			store[lower[len(lower)-1]].low = lowIndex + 1 // lower[len(lower)-1]
			lower = lower[:len(lower)-1]
		}

		// 当前index值入栈
		lower = append(lower, lowIndex)

	}

	// 还在栈内的元素说明边界
	for _, v := range higher {
		store[v].high = length - 1
	}

	for _, v := range lower {
		store[v].low = 0
	}

	for i, v := range store {
		sum = sum + (1+i-v.low+v.high-i+(v.high-i)*(i-v.low))*A[i]
	}
	return sum % 1000000007
}

单调栈介绍&性质

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • leetcode907 子数组的最小值之和
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档