1. 程式人生 > 其它 >LeetCode-2104 子陣列範圍和

LeetCode-2104 子陣列範圍和

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/sum-of-subarray-ranges

題目描述

給你一個整數陣列 nums 。nums 中,子陣列的 範圍 是子陣列中最大元素和最小元素的差值。

返回 nums 中 所有 子陣列範圍的 和 。

子陣列是陣列中一個連續 非空 的元素序列。

示例 1:

輸入:nums = [1,2,3]
輸出:4
解釋:nums 的 6 個子陣列如下所示:
[1],範圍 = 最大 - 最小 = 1 - 1 = 0
[2],範圍 = 2 - 2 = 0
[3],範圍 = 3 - 3 = 0
[1,2],範圍 = 2 - 1 = 1
[2,3],範圍 = 3 - 2 = 1
[1,2,3],範圍 = 3 - 1 = 2
所有範圍的和是 0 + 0 + 0 + 1 + 1 + 2 = 4


示例 2:

輸入:nums = [1,3,3]
輸出:4
解釋:nums 的 6 個子陣列如下所示:
[1],範圍 = 最大 - 最小 = 1 - 1 = 0
[3],範圍 = 3 - 3 = 0
[3],範圍 = 3 - 3 = 0
[1,3],範圍 = 3 - 1 = 2
[3,3],範圍 = 3 - 3 = 0
[1,3,3],範圍 = 3 - 1 = 2
所有範圍的和是 0 + 0 + 0 + 2 + 0 + 2 = 4


示例 3:

輸入:nums = [4,-2,-3,4,1]
輸出:59
解釋:nums 中所有子陣列範圍的和是 59

提示:

1 <= nums.length <= 1000
-109 <= nums[i] <= 109


解題思路

首先看資料範圍,可以通過暴力法來做,遍歷子陣列,分別求出最大值最小值然後求和,時間複雜度是O(n2)

還有一種巧妙的方法可以將時間複雜度壓縮到O(n)。

對於第i個數ai,如果左邊第一個比他小的數下標為left,第一個比他小的數下標位right,那麼(left,right)中所有的子陣列最小值都是ai,在(left,right)中共有(right - i) * (i - left) 個子陣列,那麼(left, right)範圍內子陣列最小值的和為(right - i) * (i - left) * ai,同理可以求出(left, right)範圍內子陣列最大值的和,兩個相減就可以求出(left, right)範圍內的範圍和。

問題轉化為了如何第i個數左邊小值和大值及右邊的小值和大值,使用單調棧一次遍歷便可以分別求得這四個值,並且用vector將下標存起來。

程式碼展示

暴力法:

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        int n = nums.size();
        long long ret = 0;
        for (int i = 0; i < n; i++) {
            int minVal = INT_MAX, maxVal = INT_MIN;
            for (int j = i; j < n; j++) {
                minVal = min(minVal, nums[j]);
                maxVal = max(maxVal, nums[j]);
                ret += maxVal - minVal;
            }
        }
        return ret;
    }
};

單調棧+數學:

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        int n = nums.size();
        long long ret = 0;
        vector<int> viLeftMin(n), viRightMin(n), viLeftMax(n), viRightMax(n);
        stack<int> siMax, siMin;
        for(int i = 0; i < n; i++)
        {
            while(!siMin.empty() && nums[siMin.top()] > nums[i])
                siMin.pop();
            viLeftMin[i] = siMin.empty()? -1: siMin.top();
            siMin.push(i);

            while(!siMax.empty() && nums[siMax.top()] <= nums[i])
                siMax.pop();
            viLeftMax[i] = siMax.empty()? -1: siMax.top();
            siMax.push(i);
        }
        siMax = stack<int>();
        siMin = stack<int>();
        for(int i = n - 1; i >= 0; i--)
        {
            while(!siMin.empty() && nums[siMin.top()] >= nums[i])
                siMin.pop();
            viRightMin[i] = siMin.empty()? n: siMin.top();
            siMin.push(i);

            while(!siMax.empty() && nums[siMax.top()] < nums[i])
                siMax.pop();
            viRightMax[i] = siMax.empty()? n: siMax.top();
            siMax.push(i);
        }
        
        for(int i = 0; i < n; i++)
        {
            ret += ((((long long)viRightMax[i] - i) * (i - viLeftMax[i])) - (((long long)viRightMin[i] - i) * (i - viLeftMin[i])))* nums[i];
        }
        return ret;
    }
};

執行結果