Leetcode 907:子数组的最小值之和(超详细的解法!!!)

版权声明:本文为博主原创文章,未经博主允许不得转载。有事联系:[email protected] https://blog.csdn.net/qq_17550379/article/details/86548585

给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。

由于答案可能很大,因此返回答案模 10^9 + 7

示例:

输入:[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。 

提示:

  1. 1 <= A <= 30000
  2. 1 <= A[i] <= 30000

解题思路

这个问题由于提示中的数量级已经到了10000,所以很显然不能通过暴力破解了。

class Solution:
    def sumSubarrayMins(self, A):
        """
        :type A: List[int]
        :rtype: int
        """
        A_len = len(A)
        res = 0
        for i in range(A_len):
            for j in range(i, A_len):
                res += min(A[i:j+1])
                
        return res

上面这个做法有什么问题呢?首先我们使用了min函数,这显然是一个非常耗时的操作,有没有什么办法可以记忆之前访问过的最小值,不是再从头开始检索最小值呢?我们其实很容易发现这样的规律

对于中间元素69来说,此时包含69的子数组的最小值一定都是69,也就是

73 76 72 69 
73 76 72 69 71
73 76 72 69 71 75
73 76 72 69 71 75 74 
73 76 72 69 71 75 74 73
。。。

总共有20个子数组,怎么算的呢?左边取得的最大长度是3,右边取得的最大长度是4,结果就是(3+1)*(4+1)。那么现在的问题就是怎么计算左边的最大长度和右边的最大长度?可以通过单调栈。使用什么样的单调栈呢?不难想到我们需要求左边第一个小于当前元素的位置和右边第一个小于当前元素的位置,这就需要维护一个严格单调递增的栈。参看Leetcode 单调栈问题总结(超详细!!!),我们很容易求得位置。

这个问题还有一个陷阱就是当包含重复元素的时候我们怎么计算?例如[71,55,82,55],此时有两种策略

  • nextList包含重复元素,preList不包含
  • nextList不包含重复元素,preList包含

我们这里使用的是第二种策略。

class Solution:
    def sumSubarrayMins(self, A):
        """
        :type A: List[int]
        :rtype: int
        """
        if not A:
            return 0
        
        stack = list()
        nextList = list(range(len(A), 0, -1))
        for i in range(len(A)):
            while stack and A[stack[-1]] >= A[i]:
                nextList[stack[-1]] = i - stack[-1]
                stack.pop()
            stack.append(i)
                
        preList = [-1]*len(A)
        stack.clear()
        for i in range(len(A)):
            while stack and A[stack[-1]] >= A[i]:
                stack.pop()
            if stack:
                preList[i] = i - stack[-1]
            else:
                preList[i] = i+1
            stack.append(i)
                
        return sum(i*j*k for i, j, k in zip(A, nextList, preList))%(10**9 + 7)

对于上面的这个做法,我们实际上可以继续优化,我们只需要一次遍历就可以完成。

当我们找到当前元素下一个更小值的同时,我们实际上也可以知道前一个更小的元素(因为我们使用的是递增栈)。

class Solution:
    def sumSubarrayMins(self, A):
        """
        :type A: List[int]
        :rtype: int
        """
        res = 0
        stack = []
        A = [0] + A + [0]
        for i, x in enumerate(A):
            while stack and A[stack[-1]] > x:
                j = stack.pop()
                k = stack[-1]
                res += A[j] * (i - j) * (j - k)
            stack.append(i)
        return res % (10**9 + 7)

reference:

https://leetcode.com/problems/sum-of-subarray-minimums/discuss/170750/C%2B%2BJavaPython-Stack-Solution

https://leetcode.com/problems/sum-of-subarray-minimums/discuss/178876/stack-solution-with-very-detailed-explanation-step-by-step

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/86548585
今日推荐