leetcode:907. 子数组的最小值之和

题目来源

  • 907. 子数组的最小值之和

题目描述

leetcode:907. 子数组的最小值之和_第1张图片

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {

    }
};

题目解析

暴力

既然要求每个子数组的最小值,那么直接枚举出所有的子数组即可,可以参考 动画

怎么实现呢?

  • 用双层循环,外层循环控制子数组的起始位置,内层控制子数组的结束位置
  • 然后求出每个数组的最小值,求和即可

代码:

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int ans = 0;
        
        for (int i = 0; i < arr.size(); ++i) {  // 控制子数组的起始位置
            for (int j = i; j < arr.size(); ++j) { // 控制子数组的结束位置
            	// 每个子数组的最小值
                int min = INT32_MAX;
                for (int k = 0; k <= j; ++k) {
                    min = std::min(min, arr[k]);
                }
                ans += min;
            }
        }
        return ans;
    }
};

单调栈

整体思路:

  • 依次结算以每个位置 i i i做最小值的子数组的(最小值)之和
  • 并求出各个位置的答案之和就是最终结果

以位置 i i i做最小值的子数组的(最小值)之和应该怎么计算呢?

  • a r r [ i ] arr[i] arr[i]作为最小值的子数组个数 * a r r [ i ] arr[i] arr[i]

如果想要求以 a r r [ i ] arr[i] arr[i]作为最小值的子数组个数,等价于求:

  • 位置 i i i左右两侧,小于等于 a r r [ i ] arr[i] arr[i],而且离 i i i最近的元素位置在哪里,假设分别为leftIndex、rightIndex
  • 从而我们可以得到:以位置i做最小值的子数组个数 = (i - leftIndex) * (rightIndex - i)

这个公式怎么求出来的呢?

  • 我们枚举一下能产生多少个不同的左右边界对即可
  • 子数组的左边界应该在[leftIndex + 1,i]中选取
  • 子数组的右边界应该在[i,rightIndex-1]中选取
  • 因此子数组个数个数=左边界 * 右边界 = (i - (leftIndex + 1) + 1) * ((rightIndex-1) - i + 1) = (i - leftIndex ) * (rightIndex - i)

举个例子:

例子一:

  • 当前有nums = [1,3,1,2,4,0],,假设 当前 i = 2(nums[2] = 1),则:
    • leftIndex = 0(0---->1)
    • rightIndex = 5(5----->0)
  • 那么,以 i = 2(nums[2] = 1)做最小值的子数组有:
    • 以3开头的:[3,1]、 [3,1,2]、 [3,1,2,4]
    • 以1开头的:[1]、 [1,2]、 [1,2,4]
  • 总共 2 * 3 = 6 个 ==> ( i − l e f t I n d e x ) ∗ ( r i g h t I n d e x − i ) (i - leftIndex) * (rightIndex - i) (ileftIndex)(rightIndexi)

例子2:

  • nums = [4,3,1,2,4],假设 当前 i = 2(nums[2] = 1),则:
    • leftIndex = -1(-1---->-1)
    • rightIndex = 5(5---->-1)
  • 那么,以 i = 2 (nums[2] = 3)做最小值的子数组为:
    • 以4开头的:[4,3,1]、 [4,3,1,2]、 [4,3,1,2,4]
    • 以3开头的: [3,1]、 [3,1,2]、 [3,1,2,4]
    • 以1开头的: [1]、 [1,2]、 [1,2,4]
  • 总共 3 * 3 = 9 个 ==> ( i − l e f t I n d e x ) ∗ ( r i g h t I n d e x − i ) (i - leftIndex) * (rightIndex - i) (ileftIndex)(rightIndexi)

重点:【子数组必须得跨过 i 】

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int m = arr.size();
        std::vector<int> stack(m); // 单调栈:从栈底 -> 到栈顶,单调递增
        int top = -1;  // 栈顶指针,-1表示栈空
        int ans = 0;
        // 依次结算每个位置,求以其做最小值的子数组的最小值之和是多少
        // 即求:左右两侧小于等于自己且离自己最近的位置leftIndex和rightIndex,然后套公式计算
        for (int i = 0; i < m; ++i) {
            while (top != -1 && arr[i] <= arr[stack[top]] ){  // 维持单调递增栈结构(A[i]>A[top])
                int index = stack[top]; --top; // 弹出一个元素,结算当前index位置的答案
                int leftIndex = -1;
                if(top != -1){
                    leftIndex = stack[top];
                }
                int rightIndex = i;
                // 公式计算:以[index]做最小值的子数组的最小值总和
                ans += (index - leftIndex) * (rightIndex - index) * arr[index];
                ans %= 1000000007;
            }
            stack[top + 1] = i;// 当前位置入单调栈
            top++;
        }
        // 结算栈中剩余的元素:
        while (top != -1){
            int index = stack[top]; --top; // 弹出一个元素,结算当前index位置的答案
            int leftIndex = -1;
            if(top != -1){
                leftIndex = stack[top];
            }
            int rightIndex = m;
            // 公式计算:以[index]做最小值的子数组的最小值总和
            ans += (index - leftIndex) * (rightIndex - index) * arr[index];
            ans %= 1000000007;
        }
        return ans;
    }
};









本题主要是找到左边比自己小和右边比自己小的第一个元素下标就好;
难点:

  • 1.对于相等的情况如何去重且不遗漏
  • 2.为什么是(i - left[i]) * (right[i] - i) * arr[i])

问题:如果出现了重复值怎么处理呢?

  • 比如A= [ 5 , 4 , 4 , 6 , 7 , 8 , 4 , 4 , 2 ] [5,4,4,6,7,8,4,4,2] [5,4,4,6,7,8,4,4,2]
  • 以A[1]=4作为最小值,定下左右边界后是 [ 5 , 4 , 4 , 6 , 7 , 8 , 4 , 4 ] [5,4,4,6,7,8,4,4] [5,4,4,6,7,8,4,4]
  • 以A[2]=4作为最小值,定下左右边界后也是 [ 5 , 4 , 4 , 6 , 7 , 8 , 4 , 4 ] [5,4,4,6,7,8,4,4] [5,4,4,6,7,8,4,4]
  • 以A[6]=4作为最小值,定下左右边界后也是 [ 5 , 4 , 4 , 6 , 7 , 8 , 4 , 4 ] [5,4,4,6,7,8,4,4] [5,4,4,6,7,8,4,4];
  • 以A[7]=4作为最小值,定下左右边界后是 [ 5 , 4 , 4 , 6 , 7 , 8 , 4 , 4 ] [5,4,4,6,7,8,4,4] [5,4,4,6,7,8,4,4]

要避免这种重复,可以约定往左边找的时候,如果发现相同元素就停下且不能包括相同元素在边界内,而右边仍然可以:

  • 以A[1]=4作为最小值,定下左右边界后是 [ 5 , 4 , 4 , 6 , 7 , 8 , 4 , 4 ] [5,4,4,6,7,8,4,4] [5,4,4,6,7,8,4,4]
  • 以A[2]=4作为最小值,定下左右边界后也是 [ 4 , 6 , 7 , 8 , 4 , 4 ] [4,6,7,8,4,4] [4,6,7,8,4,4]
  • 以A[6]=4作为最小值,定下左右边界后也是 [ 6 , 7 , 8 , 4 , 4 ] [6,7,8,4,4] [6,7,8,4,4];
  • 以A[7]=4作为最小值,定下左右边界后是 [ 4 ] [4] [4]
class Solution {
    std::vector<int> nearLessEqualLeft(vector<int>& arr, vector<int>&stack){
        int m = arr.size();
        std::vector<int> left(m, 0);
        int size = 0;
        for (int i = m - 1; i >= 0; --i) {
            while (size != 0 && arr[i] <= arr[stack[size - 1]]) {
                left[stack[--size]] = i;
            }
            stack[size++] = i;
        }
        while (size != 0) {
            left[stack[--size]] = -1;
        }
        return left;
    }

    std::vector<int> nearLessRight(vector<int>& arr, vector<int>&stack){
        int m = arr.size();
        std::vector<int> right(m, 0);
        int size = 0;
        for (int i = 0; i < m; i++) {
            while (size != 0 && arr[stack[size - 1]] > arr[i]) {
                right[stack[--size]] = i;
            }
            stack[size++] = i;
        }
        while (size != 0) {
            right[stack[--size]] = m;
        }
        return right;
    }
public:
    int sumSubarrayMins(vector<int>& arr) {
        int m = arr.size();
        std::vector<int> stack(m, 0);
        auto left = nearLessEqualLeft(arr, stack);
        auto right = nearLessRight(arr, stack);
        long ans = 0;
        for (int i = 0; i < m; i++) {
            long start = i - left[i];
            long end = right[i] - i;
            ans += start * end * (long) arr[i];
            ans %= 1000000007;
        }
        return (int) ans;
    }
};

你可能感兴趣的:(算法与数据结构,leetcode,算法,职场和发展)