【2022-10-28每日一题】907. 子数组的最小值之和[Medium]

2022-10-28
3分钟阅读时长

2022-10-28每日一题:907. 子数组的最小值之和

难度:Medium

标签:栈 、 数组 、 动态规划 、 单调栈

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

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

 

示例 1:

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

示例 2:

输入:arr = [11,81,94,43,3]
输出:444

 

提示:

  • 1 <= arr.length <= 3 * 104
  • 1 <= arr[i] <= 3 * 104

 

方法一:单调栈

官方优化版

const mod int = 1e9+7
func sumSubarrayMins(arr []int) int {
    n := len(arr)
    left, right := make([]int, n), make([]int, n)
    stack := []int{-1} // -1 为哨兵
    for i := 0; i < n; i++ {
        for len(stack) > 1 && arr[stack[len(stack)-1]] >= arr[i] {
            stack = stack[:len(stack)-1]
        }
        left[i] = i - stack[len(stack)-1]
        stack = append(stack, i)
    }
    stack = []int{n}
    for i := n - 1; i >= 0; i-- {
        for len(stack) > 1 && arr[stack[len(stack)-1]] > arr[i] {
            stack = stack[:len(stack)-1]
        }
        right[i] = stack[len(stack)-1] - i
        stack = append(stack, i)
    }
    ans := 0
    for i, x := range arr {
        ans = (ans + x * left[i] * right[i]) % mod
    }
	return ans
}

延迟计算宽度

const mod int = 1e9+7
func sumSubarrayMins(arr []int) int {
    n := len(arr)
    left, right := make([]int, n), make([]int, n)
    stack := []int{-1} // -1 为哨兵
    for i := 0; i < n; i++ {
        for len(stack) > 1 && arr[stack[len(stack)-1]] >= arr[i] {
            stack = stack[:len(stack)-1]
        }
        left[i] = stack[len(stack)-1]
        stack = append(stack, i)
    }
    stack = []int{n}
    for i := n - 1; i >= 0; i-- {
        for len(stack) > 1 && arr[stack[len(stack)-1]] > arr[i] {
            stack = stack[:len(stack)-1]
        }
        right[i] = stack[len(stack)-1]
        stack = append(stack, i)
    }
    ans := 0
    for i, x := range arr {
        ans = (ans + x * (i - left[i]) * (right[i]-i)) % mod
    }
	return ans
}

二次遍历变形

func sumSubarrayMins(arr []int) (ans int) {
    n := len(arr)
    left := make([]int, n)
    right := make([]int, n)
    for i := range right {
        right[i] = n
    }
    st := []int{-1} // -1 为哨兵,方便计算赋值
    for i, x := range arr {
        for len(st) > 1 && arr[st[len(st)-1]] >= x {
            right[st[len(st)-1]] = i // i 恰好是栈顶的右边界
            st = st[:len(st)-1]
        }
        left[i] = st[len(st)-1]  // 可以直接计算 i - st[len(st)-1]
        st = append(st, i)
    }

    for i, x := range arr {
        ans += x * (i - left[i]) * (right[i] - i) // 累加贡献
    }
    return ans % (1e9 + 7)
}

优化版本:一次遍历

func sumSubarrayMins(arr []int) (ans int) {
    arr = append(arr, -1)
    st := []int{-1} // 哨兵
    for r, x := range arr {
        for len(st) > 1 && arr[st[len(st)-1]] >= x {
            i := st[len(st)-1]
            st = st[:len(st)-1]
            ans += arr[i] * (i - st[len(st)-1]) * (r - i) // 累加贡献
        }
        st = append(st, r)
    }
    return ans % (1e9 + 7)
}

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

方法二:动态规划

const mod int = 1e9 + 7
func sumSubarrayMins(arr []int) (ans int) {
    n := len(arr)
    dp := make([]int, n)
    stack := []int{} // 栈中保持数组索引
    for i, x := range arr {
        // 移除栈顶比当前数大的所有数
        for len(stack) > 0 && arr[stack[len(stack)-1]] > x {
            stack = stack[:len(stack)-1]
        }
        k := i + 1 // 栈为空的k值
        if len(stack) > 0 {
            k = i - stack[len(stack)-1]
        }
        dp[i] = k * x
        if len(stack) > 0 {
            dp[i] += dp[i-k]
        }
        ans = (ans+dp[i]) % mod
        stack = append(stack, i)
    }
    return ans
}

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

LeetCode题库地址

推荐阅读题解