问题:
给定数组,求所有连续子数组的最小值之和。
若所得之数太大,求其mod(10^9 + 7)
Example 1: Input: [3,1,2,4] Output: 17 Explanation: Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1. Sum is 17. Note: 1 <= A.length <= 30000 1 <= A[i] <= 30000
解法1:
对于数组中的每一个值A[i]
当它为最小值时,
有:向左连续包含它,且所有元素>=它,的元素个数left[i];
有:向右连续包含它,且所有元素>=它,的元素个数right[i];
那么当它为最小值的,连续子数组的个数=left[i]*right[i] (因为要包含它,且为连续的子数组)
那么最小值之和为:A[i]*left[i]*right[i]
同理遍历所有元素,则可得整个数组的最小值之和
1 for(int i=0; i<A.size(); i++){ 2 res=(res+ (A[i]*left[i]*right[i]))%mod; 3 } 4 return res;
接下来,需要构建left[i]和right[i]
我们使用递增stack来求得。
left[i] :A[i]以左,所有元素>=A[i]
right[i]:A[i]以右,所有元素>=A[i]
递增stack:(最终形态)栈内元素:为从原数组最小值起递增,
我们可以同时记录:从上一个栈内元素 stack[i-1] 到该元素 stack[i] 为止,数组内元素的个数,
这些数num一定大于stack[i],即:num>=stack[i]>stack[i-1]
那么,A[i]的left[i]或者right[i]则=stack[i]+...stack[i-x] (stack[i-x]>=A[i])的累加即可。
下面代码的 increasingL 代表 stack:
1 for(int i=0; i<A.size(); i++){ 2 int cout=1; 3 while(!increasingL.empty() && increasingL.top().first>A[i]){ 4 cout+=increasingL.top().second; 5 increasingL.pop(); 6 } 7 left[i]=cout; 8 increasingL.push({A[i],cout}); 9 }
left从左向右存储:左边所有大于A[i]的元素个数
right从右向左存储:右边所有大于A[i]的元素个数
方法一样,只是有一个需要注意的点:
⚠️注意:对于数组中存在重复的数字时,如何处理:
stack的pop时机:一个>= 另一个>
这样,会使得,left的包含两个相同的数值,right不包含两个相同的数值。
对于相同的A[i],A[j],一边包含相同数值,一边不包含,相比两边都包含,
会减去一次重复计算的两个数都包含的情况。详细请参考下面的“1”
代码参考:
1 class Solution { 2 public: 3 int sumSubarrayMins(vector<int>& A) { 4 int res=0, mod = (int)1e9 + 7; 5 vector<int> left(A.size()); 6 vector<int> right(A.size()); 7 stack<pair<int,int>> increasingL,increasingR; 8 //(num,从最小的数开始,stack上一个num为止>本值(比自己大)的个数cout) 9 //e.g.[3,2,1,2,1,4,5,2,3,6] 10 // ^ ^ ^ ^ ^ 11 // cout: 3 2 3 1 1 12 // cout([start~1]:>1+自己)=3:[3,2,1] 13 // cout((1~1] :>1+自己)=2:[2,1] 14 // cout((1~2] :>2+自己)=3:[4,5,2] 15 // cout((2~3] :>3+自己)=1:[3] 16 // cout((3~6] :>6+自己)=1:[6] 17 //stack:[{1,cout:3},{1,cout:2},{2,cout:3},{3,cout:1},{6,cout:1}] 18 for(int i=0; i<A.size(); i++){ 19 int cout=1; 20 while(!increasingL.empty() && increasingL.top().first>A[i]){ 21 //stack:[{1,cout:3},{1,cout:2},{2,cout:3},{3,cout:1},{6,cout:1}] 22 //==的话,就不pop,那么会存储两个1 23 cout+=increasingL.top().second; 24 increasingL.pop(); 25 } 26 left[i]=cout; 27 increasingL.push({A[i],cout}); 28 } 29 for(int i=A.size()-1; i>=0; i--){ 30 int cout=1; 31 while(!increasingR.empty() && increasingR.top().first>=A[i]){ 32 //e.g. [3,2,1,2,1,4,5,2,3,6] 33 // ^ ^ ^ 34 // cout: 1 1 8 35 //cout([end~1]:>=1)=8:[1,2,1,4,5,2,3,6] 36 //cout((1~2] :>=2)=1:[2] 37 //cout((2~3] :>=3)=1:[3] 38 //stack:[{1,cout:8},{2,cout:1},{3,cout:1}] 39 //==的话,pop,那么会存储一个1 40 //left[第一个1]=3[3,2,1],right[第一个1]=8[1,2,1,4,5,2,3,6] 41 //包含了另一个1->所得子数组包含另一个1 42 //left[第二个1]=2[2,1],right[第二个1]=6[1,4,5,2,3,6] 43 //不包含另一个1->所得子数组不包含另一个1 44 cout+=increasingR.top().second; 45 increasingR.pop(); 46 } 47 right[i]=cout; 48 increasingR.push({A[i],cout}); 49 } 50 for(int i=0; i<A.size(); i++){ 51 res=(res+ (A[i]*left[i]*right[i]))%mod; 52 } 53 return res; 54 } 55 };
解法2:
动态规划:
dp[i+1] :以数字 A[i] 结尾的所有子数组最小值之和。
动态转移方程:
dp[i+1]=(dp[index+1]+(i-index)*A[i])%mod;
解释:
以数字 A[i] 结尾的所有子数组,分为两个部分:
1⃣️,A[i]为最小值的所有子数组
因为要连续:★那么找 i 以前的,第一个小于A[i]的数的位置:index,那么有 i-index 个子数组。
最小值之和= (i-index) * A[i]
e.g. [3,1,4,5,2],对于A[i]=2(i=4)来说,第一个小于2的数为1(index=1),
那么有1以后的数[4,5,2]可做成个子数组 i-index = 4-1 =3个数组:[2] [5,2] [4,5,2]
2⃣️,比A[i]还小的值,做为最小值的所有子数组
index之前的数,最小值一定是比A[i]小。
因为要连续,且以A[i]为结束元素,
那么,dp[index+1]所表示的所有到A[index]为结尾的子数组,
再拼上A[index+1]~A[i]的所有元素,组成新的子数组。
这些子数组的最小值< min(A[index+1]~A[i])=A[i] 不变,因此最小值之和仍然=dp[index+1],也不变。
最后,求所有子数组=Sum(每一个A[i]为结尾的子数组最小值之和)=Sum[dp[i]]
其中★,的实现,仍然同解法1,使用递增stack。这里,我们存入元素的index值。
每读入一个小于栈顶元素,栈顶出栈,
直到读入大于栈顶的元素,入栈。
得到的栈元素之间的【未入栈的元素】皆大于【两个栈元素】。
代码参考:
1 class Solution { 2 public: 3 int sumSubarrayMins(vector<int>& A) { 4 int res=0, mod = (int)1e9 + 7; 5 stack<int> increasingL;//stack.top()栈顶:上一个比自己值小的index 6 //到自己i为止:i-index=连续比自己大的元素个数=子数组个数 7 increasingL.push(-1); 8 int dp[A.size()+1]; 9 dp[0]=0; 10 //dp[i] 表示以数字 A[i-1] 结尾的所有子数组最小值之和。 11 //=dp[index]//index之前的各数组的最小值不变,这些数组合并到i为止的元素组成新的子数组。因此和不变 12 // + 自己为最小的子数组个数:(i-index)*A[i] 13 //so:dp[i+1]=dp[index+1]+(i-index)*A[i] 14 for(int i=0; i<A.size(); i++){ 15 int index; 16 while(increasingL.top()!=-1 && A[increasingL.top()]>=A[i]){ 17 increasingL.pop(); 18 } 19 index=increasingL.top(); 20 dp[i+1]=(dp[index+1]+(i-index)*A[i])%mod; 21 res=(res+dp[i+1])%mod; 22 increasingL.push(i); 23 } 24 return res; 25 } 26 };