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

难度:中等。
标签:栈,数组。

思路很好想,就是超时。

正向思维

找出所有的子数组,找出所有子数组中的最小值,求他们的总和。

首先动态规划,超时了。
超时代码:

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        int base = pow(10, 9) + 7;
        vector<vector<int>> dp(n, vector<int>(n));
        int res = 0;
        for(int i = 0; i < n; i++){
            dp[i][i] = arr[i];
            res += dp[i][i];
            if(res >= base)res %= base;
        }

        for(int step = 1; step < n; step++){
            for(int i = 0; i < n - step; i++){
                int j = i + step;
                dp[i][j] = min(arr[j], dp[i][j - 1]);
                res += dp[i][j];
                if(res >= base)res %= base;
            }
        }
        return res;
    }
};

然后改用递归做,依旧超时了。
超时代码:

class Solution {
    int base = pow(10, 9) + 7;

    int getResult(vector<int>& arr){
        int n = arr.size();
        if(n == 0 )return 0;
        int min_idx = 0, min_num = arr[0];
        for(int i = 1; i < arr.size(); i++){
            if(min_num > arr[i]){
                min_num = arr[i];
                min_idx = i;
            }
        }
        vector<int> left(arr.begin(), arr.begin() + min_idx);
        vector<int> right(arr.begin() + min_idx + 1, arr.end());
        int res = ((min_idx + 1) * (n - min_idx)) * min_num;
        if(res >= base)res %= base;

        int res1 = getResult(left), res2 = getResult(right);
        res += res1;
        if(res >= base)res %= base;
        res += res2;
        if(res >= base)res %= base;
        return res;
    }

public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        
        int res = getResult(arr);

        return res;
    }
};

逆向思路

找出所有的最小值,找出最小值所在的数组的个数,求出他们的和。

想到了另一种思路,依次查找每个元素左右第一个小于该元素值的位置,然后就可以得到该元素作为最小元素出现了几回。但这个思路有问题,当出现重复的元素时,会多计算一些序列。
错误代码:


class Solution {
    int base = pow(10, 9) + 7;

public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();

        vector<int> left_low(n), right_low(n);
        left_low[0] = -1;
        for(int i = 1; i < n; i++){
            int j = i - 1;
            left_low[i] = -1;
            while(j >= 0){
                if(arr[j] < arr[i]){
                    left_low[i] = j;
                    break;
                }
                j = left_low[j];
            }
        }
        right_low[n - 1] = n;
        for(int i = n - 2; i >= 0; i--){
            int j = i + 1;
            right_low[i] = n;
            while(j < n){
                if(arr[j] < arr[i]){
                    right_low[i] = j;
                    break;
                }
                j = right_low[j];
            }
        }

        /*for(int i = 0; i < n; i++){
            cout << i << " " << left_low[i] << " " << right_low[i] << endl;
        }*/

        long res = 0;
        for(int i = 0; i < n; i++){
            res += (long)(i - left_low[i]) * (right_low[i] - i) * arr[i];
            res %= base;
        }
        
        return res;
    }
};

看了题解,题解用的方法好难理解啊。
使用单调栈来做,单调栈存储数组的索引,push进递增的数的索引。
遍历数组

  • 如果stk为空,则将数组当前值的索引push进栈中。
  • 如果stk不为空,且栈顶索引 j j j对应的元素大于当前值,说明以栈顶元素为最小值的序列到当前位置的前一个。将当前栈顶元素 j j jpop出,下一个栈顶元素 p r e v j prevj prevj是左边第一个比当前栈顶元素小的值的索引,这样就得到了索引 j j j处元素为最小值的最长序列,由此可得arr[j]出现了几回。

用这种单调栈可以找出数组每个元素的上一个或下一个大于或小于该元素的索引位置。

class Solution {
    int base = pow(10, 9) + 7;

public:
    int sumSubarrayMins(vector<int>& arr) {
        stack<int> stk;

        arr.push_back(0);
        long res = 0;
        int n = arr.size();
        for(int i = 0; i < n; i++){
            while(!stk.empty() && arr[i] <= arr[stk.top()]){
                int j = stk.top();
                stk.pop();
                int prev_index = -1;
                if(!stk.empty())prev_index = stk.top();
                res += (long)(j - prev_index) * (i - j) * arr[j];
                res %= base;
            }
            stk.push(i);
        }
        return res;
    }
};

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

你可能感兴趣的:(刷题)