如何破局ERP与MES系统集成之“锁”?从“数据孤岛”到“生产大脑”的深度集成之路
2026/1/8 17:26:34
给定一个整数数组arr,计算所有非空连续子数组的最小值之和。由于答案可能很大,返回结果对10^9 + 7取模。
示例:
输入: 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。单调栈:
arr[i],计算它作为最小值能"贡献"到多少个子数组中arr[i]的位置left和右边第一个小于等于arr[i]的位置rightarr[i]为最小值的子数组数量为(i - left) * (right - i)arr[i] * (i - left) * (right - i)为什么右边用"小于等于"而左边用"小于"?
classSolution{privatestaticfinalintMOD=1000000007;/** * 计算所有子数组最小值之和 * 使用单调栈计算每个元素的贡献度 * * @param arr 输入数组 * @return 所有子数组最小值之和(对10^9+7取模) */publicintsumSubarrayMins(int[]arr){intn=arr.length;// left[i] 表示 arr[i] 左边第一个小于 arr[i] 的元素位置,不存在则为 -1int[]left=newint[n];// right[i] 表示 arr[i] 右边第一个小于等于 arr[i] 的元素位置,不存在则为 nint[]right=newint[n];// 计算 left 数组 - 使用单调递增栈Deque<Integer>stack=newArrayDeque<>();for(inti=0;i<n;i++){// 维护单调递增栈,弹出大于等于当前元素的索引while(!stack.isEmpty()&&arr[stack.peek()]>=arr[i]){stack.pop();}// 如果栈为空,说明左边没有更小的元素left[i]=stack.isEmpty()?-1:stack.peek();stack.push(i);}// 清空栈,准备计算 right 数组stack.clear();// 计算 right 数组 - 使用单调递增栈for(inti=n-1;i>=0;i--){// 维护单调递增栈,弹出大于当前元素的索引while(!stack.isEmpty()&&arr[stack.peek()]>arr[i]){stack.pop();}// 如果栈为空,说明右边没有更小或相等的元素right[i]=stack.isEmpty()?n:stack.peek();stack.push(i);}longresult=0;// 计算每个元素的贡献度for(inti=0;i<n;i++){// 以 arr[i] 为最小值的子数组数量longcount=(long)(i-left[i])*(right[i]-i);// 累加贡献度result=(result+arr[i]*count)%MOD;}return(int)result;}}classSolution{privatestaticfinalintMOD=1000000007;/** * 在单调栈弹出元素时直接计算贡献度 * * @param arr 输入数组 * @return 所有子数组最小值之和(对10^9+7取模) */publicintsumSubarrayMins(int[]arr){intn=arr.length;Deque<Integer>stack=newArrayDeque<>();longresult=0;// 添加哨兵元素简化边界处理for(inti=0;i<=n;i++){// 当 i == n 时,当前值设为 -1(确保弹出所有剩余元素)intcurrent=(i==n)?-1:arr[i];// 当栈不为空且当前元素小于栈顶元素时while(!stack.isEmpty()&¤t<arr[stack.peek()]){// 弹出栈顶元素intmid=stack.pop();// 左边界:栈顶元素(如果栈为空则为 -1)intleft=stack.isEmpty()?-1:stack.peek();// 右边界:当前索引 iintright=i;// 计算以 arr[mid] 为最小值的子数组数量longcount=(long)(mid-left)*(right-mid);result=(result+(long)arr[mid]*count)%MOD;}stack.push(i);}return(int)result;}}输入:arr = [3,1,2,4]
方法一:
计算 left 数组:
i=0: 栈空 →left[0] = -1, 栈: [0]i=1:arr[0]=3 >= arr[1]=1→ 弹出0,栈空 →left[1] = -1, 栈: [1]i=2:arr[1]=1 < arr[2]=2→left[2] = 1, 栈: [1,2]i=3:arr[2]=2 < arr[3]=4→left[3] = 2, 栈: [1,2,3]left = [-1, -1, 1, 2]计算 right 数组:
i=3: 栈空 →right[3] = 4, 栈: [3]i=2:arr[3]=4 > arr[2]=2→ 弹出3,栈空 →right[2] = 4, 栈: [2]i=1:arr[2]=2 > arr[1]=1→ 弹出2,栈空 →right[1] = 4, 栈: [1]i=0:arr[1]=1 < arr[0]=3→right[0] = 1, 栈: [1,0]right = [1, 4, 4, 4]计算贡献度:
i=0:count = (0-(-1)) * (1-0) = 1*1 = 1, 贡献 =3*1 = 3i=1:count = (1-(-1)) * (4-1) = 2*3 = 6, 贡献 =1*6 = 6i=2:count = (2-1) * (4-2) = 1*2 = 2, 贡献 =2*2 = 4i=3:count = (3-2) * (4-3) = 1*1 = 1, 贡献 =4*1 = 43+6+4+4 = 17publicstaticvoidmain(String[]args){Solutionsolution=newSolution();// 测试用例1:标准示例int[]arr1={3,1,2,4};System.out.println("Test 1: "+solution.sumSubarrayMins(arr1));// 17// 测试用例2:递增数组int[]arr2={1,2,3,4};System.out.println("Test 2: "+solution.sumSubarrayMins(arr2));// 20// [1]+[2]+[3]+[4]+[1,2]+[2,3]+[3,4]+[1,2,3]+[2,3,4]+[1,2,3,4]// = 1+2+3+4+1+2+3+1+2+1 = 20// 测试用例3:递减数组int[]arr3={4,3,2,1};System.out.println("Test 3: "+solution.sumSubarrayMins(arr3));// 20// 每个子数组的最小值都是最后一个元素// 测试用例4:相同元素int[]arr4={2,2,2};System.out.println("Test 4: "+solution.sumSubarrayMins(arr4));// 12// [2]+[2]+[2]+[2,2]+[2,2]+[2,2,2] = 2+2+2+2+2+2 = 12// 测试用例5:单元素int[]arr5={5};System.out.println("Test 5: "+solution.sumSubarrayMins(arr5));// 5// 测试用例6:大数值(测试取模)int[]arr6={100000};System.out.println("Test 6: "+solution.sumSubarrayMins(arr6));// 100000// 测试用例7:复杂情况int[]arr7={71,55,82,55};System.out.println("Test 7: "+solution.sumSubarrayMins(arr7));// 539// 测试用例8:包含0int[]arr8={3,0,2,1};System.out.println("Test 8: "+solution.sumSubarrayMins(arr8));// 7}贡献度:
单调栈:
边界处理:
(i-left) * (right-i)数据类型:
long防止中间计算溢出10^9 + 7取模哨兵:
为什么右边用"小于等于"而左边用"小于"?
(i-left) * (right-i)公式?
(i-left)表示以arr[i]为右端点的左边界选择数量(right-i)表示以arr[i]为左端点的右边界选择数量arr[i]且arr[i]为最小值的子数组数量