2281. 巫师的总力量和(力扣第 294 场周赛第 4 题)

这道题做起来比较复杂。需要单调栈、前缀和、数学计算。

一句话概括思路:在 907. 子数组的最小值之和 中,是对每个 min 乘以管辖范围内子数组的个数,现在是对每个min 乘以管辖范围内所有子数组的总和,这就需要前缀和的计算。

大致思想是这样的:一个子数组被遍历的标识,就是计算了这个子数组的最小值 × 和。只要把所有最小值和对应子数组的和给遍历完了,整个数组就遍历完了。

现在,我们有最小值 arr[0],最小值 arr[1],…… 最小值 arr[n - 1],每个都唯一对应着一个管辖范围(区间),这个区间上,有很多子数组。设区间左端点下标为 L,右端点下标为 R,可计算有 (i - L + 1) * (R - i + 1) 个子数组。这些子数组的和是可以求出来的。

每个子数组的和,可以通过两端点的前缀和计算得到。因此一共对应 2 * (i - L + 1) * (R - i + 1) 个前缀和。这个式子是 O(N^2) 的时间复杂度,然后 i 还要从 0 遍历到 len - 1,共 O(N^3) 时间复杂度,不可取:

for(i: 0 to arr.length - 1)计算 L, R// 遍历 L 到 R 上的所有子数组,将和加入 resfor(l: L to i)for(r: i to R)res += sum(arr, j, k)

计算和的那两个 for 循环能否优化一下呢?我们发现,sum(arr, l, r) 是通过前缀和来计算的。我们可计算原数组 arr 的前缀和 s,其中 s[i] 表示 arr[0] + arr[1] + … + arr[i]。那么 sum(arr, l, r) 可以拆开来:s[r]-s[l - 1]。两个 for 循环拿数学表达式写一下,就是这个样子: ∑ j = L i ∑ k = i R s [ r ] − s [ l − 1 ] \sum_{j=L}^{i}\sum_{k=i}^{R} s[r]-s[l-1] ∑j=Li​∑k=iR​s[r]−s[l−1]。化简得 ( i − L + 1 ) ∑ r = i R s [ r ] − ( R − i + 1 ) ∑ l = L i s [ l − 1 ] (i-L+1)\sum_{r=i}^{R}s[r]-(R-i+1)\sum_{l=L}^{i}s[l-1] (i−L+1)∑r=iR​s[r]−(R−i+1)∑l=Li​s[l−1]。其中 ∑ r = i R s [ r ] \sum_{r=i}^{R}s[r] ∑r=iR​s[r]和 ∑ l = L i s [ l − 1 ] \sum_{l=L}^{i}s[l-1] ∑l=Li​s[l−1]都是前前缀和,在线算法,时间空间复杂度都是 O(N)。

通过数学计算,整个问题的时间复杂度压缩到 O(N)。

但为什么能通过数学计算来压缩呢?我们可以画个图,然后就明白了。

当确定 i 对应的 L 和 R 后,l 和 r 就可以在 [L, i] 和 [i, R] 上移动以形成子数组。把这两段长度相乘,也就得到 (i - L + 1) * (R - i + 1),也就是前面说的子数组个数。

那么,我们固定 l,移动 r 时,以上图 5 个子数组为例,它们的和为 (s[i] - s[L]) + (s[i + 1] - s[L]) + (s[i + 2] - s[L]) + (s[i + 3] - s[L]) + (s[i + 4] - s[L]) 。如果不是这样的计算步骤,而是先把 5 个 s[r] 的值累加,然后再减去 5 个 s[l],那么结果会是:s[i] + s[i + 1] + s[i + 2] + s[i + 3] + s[i + 4] - 5 * s[L]

其中,s[i] + s[i + 1] + s[i + 2] + s[i + 3] + s[i + 4]前缀和数组计算某个区间内的总和问题,因此可通过前前缀和来解决。

Java 代码如下:

public class Solution {int mod = 1000000007;/* 通过原数组,计算前缀和数组,以及通过前缀和数组,计算前前缀和数组。 */public int[] getPrefixSum(int[] arr) {if (arr.length == 0) {return new int[0];}int[] preSum = new int[arr.length];preSum[0] = arr[0];for (int i = 1; i < arr.length; i++) {preSum[i] = (preSum[i - 1] + arr[i]) % mod;}return preSum;}/* 通过前缀和数组计算区间和 */public int getSum(int[] preSum, int left, int right) {if (right < 0) {return 0;}if (left <= 0) {return preSum[right];}return (preSum[right] - preSum[left - 1] + mod) % mod;}/* 计算“管辖区域” */public void getCorner(int[] arr, int[] left, int[] right) {Stack<Integer> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {// pop up some bigger value from the stackwhile (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {int pop = stack.pop();// get corner of popint leftCorner = 0;if (!stack.isEmpty()) {leftCorner = stack.peek() + 1;}left[pop] = leftCorner;right[pop] = i - 1;}stack.push(i);}while (!stack.isEmpty()) {int pop = stack.pop();int leftCorner = 0;if (!stack.isEmpty()) {leftCorner = stack.peek() + 1;}left[pop] = leftCorner;right[pop] = arr.length - 1;}}/* 计算前缀和数组和前前缀和数组,以及管辖区域,然后对每个 arr[i],计算以 arr[i] 为 min 的子数组的和,再与 min 相乘。最后累加到 res 中。 */public int totalStrength(int[] arr) {int[] preSum = getPrefixSum(arr);int[] prePreSum = getPrefixSum(preSum);int[] left = new int[arr.length];int[] right = new int[arr.length];getCorner(arr, left, right);int res = 0;for (int i = 0; i < arr.length; i++) {int L = left[i];int R = right[i];res = (int) ((res + arr[i] * ((long) (i - L + 1) *getSum(prePreSum, i, R) % mod - (long) (R - i + 1) *getSum(prePreSum, L - 1, i - 1) %mod + mod) % mod) % mod);}return res;}
}

(注:写作过程中,参考以下资料:
https://www.bilibili.com/video/BV1RY4y157nW?t=1065.7)

当单调栈遇到了前前缀和(Leetcode 2281. 巫师的总力量和、力扣第 294 场周赛第 4 题)相关推荐

  1. 2281. 巫师的总力量和(单调栈+前缀和的前缀和)

    2281. 巫师的总力量和 文章目录 题目描述 示例 数据范围 思路 代码 题目描述 示例 数据范围 1<= strength.length <= 105 1 <= strength ...

  2. 力扣 2281. 巫师的总力量和 前缀和的前缀和

    2281. 巫师的总力量和 作为国王的统治者,你有一支巫师军队听你指挥. 给你一个下标从 0 开始的整数数组 strength ,其中 strength[i] 表示第 i 位巫师的力量值.对于连续的一 ...

  3. 力扣 第314场周赛 Q3 使用机器人打印字典序最小的字符串【难度:中等,rating: 1953】(栈+贪心)

    题目链接 https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string/ 题目来源 ...

  4. 【JAVA】力扣第198场周赛代码+解题思路——【排名第 1 ~ 300 名的参赛者可获「微软中国」简历内推机会】做对前两道就能排到268/ 5778(4.6%)

    目录 前言 一.题目:5464. 换酒问题 题解 代码 二.题目:5465. 子树中标签相同的节点数 题解 代码 三.题目:5466. 最多的不重叠子字符串 题解 代码 四.5467. 找到最接近目标 ...

  5. leetcode第294场周赛巫师的总力量和——维护前缀和的前缀和

    主要记录一下On预处理,O1取任意区间前缀和的前缀和 题目 6077. 巫师的总力量和 解题思路 贪心从小到大考虑,每次计算以 a i a_i ai​为最小值时的贡献. 下面来看具体例子: 考虑 a ...

  6. 巫师的总力量和【单调栈+前缀和的前缀和】

    搞懂这个题需要先搞懂P1856子数组最小乘积的最大值,关于P1856和单调栈的知识可以见上一篇关于单调栈的文章寻找下一个最大/小问题[单调栈] 此题目是leetcode294场周赛T4,题目在巫师的总 ...

  7. LeetCode第 57 场力扣夜喵双周赛(差分数组、单调栈) and 第 251 场力扣周赛(状态压缩动规,树的序列化,树哈希,字典树)

    LeetCode第 57 场力扣夜喵双周赛 离knight勋章越来越近,不过水平没有丝毫涨进 1941. 检查是否所有字符出现次数相同 题目描述 给你一个字符串 s ,如果 s 是一个 好 字符串,请 ...

  8. LeetCode 第 58 场力扣夜喵双周赛(动态规划、马拉车算法,前后缀处理)/ 第 253 场力扣周赛(贪心,LIS)

    第 58 场力扣夜喵双周赛 两道600多 5193. 删除字符使字符串变好 题目描述 一个字符串如果没有 三个连续 相同字符,那么它就是一个 好字符串 . 给你一个字符串 s ,请你从 s 删除 最少 ...

  9. LeetCode 第 59 场力扣夜喵双周赛(最短路径数+迪杰斯特拉、动态规划+最长公共前缀问题) / 第255场周赛(二进制转换,分组背包,子集还原数组(脑筋急转弯))

    第 59 场力扣夜喵双周赛 两道400多五百,后两道都写出代码来了,但是都有问题,哭辽- 还有刚开始第一道测试好慢,搞心态了 5834. 使用特殊打字机键入单词的最少时间 有一个特殊打字机,它由一个 ...

最新文章

  1. 开机报警disk boot sector is to be modified
  2. 趣学python3(37)-合并所有目录及子目录的文本文件为一个文件
  3. 全国计算机二级vb得分技巧,全国计算机等级考试二级VB笔试各题型答题技巧(2)...
  4. 【maven3学习之三】maven构建一个简单的Hello World
  5. MySQL 实用语句集合
  6. pta l2-6(树的遍历)
  7. RMI和WebService
  8. python3.6找到不_sqlite3模块
  9. python基础-PyCharm设置作者信息模板_修改解释器_设置软件UTF-8编码
  10. 【bzoj4753】[Jsoi2016]最佳团体 分数规划+树形背包dp
  11. 基于SSM的景区旅游管理系统
  12. Java数据库编程技术 第四章习题
  13. Redis中的lua脚本
  14. 使用Excel背单词-高效-简单
  15. i510300h和i78750h参数对比哪个好
  16. word整个表格首行缩进_Word段落首行无法缩进、不能输入空格与表格首行缩进的解决方法...
  17. 坐等破亿!华为鸿蒙系统用户升级量已突破1800万,你要升级吗?
  18. Windows注册表知识
  19. 原生js实现购物车添加删除商品、计算价格功能
  20. Windows Server 2016搭建文件服务器

热门文章

  1. Word2Vec报错:KeyError: “word ‘XXX‘ not in vocabulary“
  2. 关于内存的编程题,对异常: 0xC0000005 的分析以及解决办法
  3. BeeWare官方教程中文版
  4. 想要注册邮箱怎么弄 邮箱如何可以撤回邮件呢
  5. Mac开启或关闭SIP
  6. 超声后端图像处理-概述
  7. 网络管理怎么配置路由
  8. 技术分享:微信小程序音视频与WebRTC互通的技术思路和实践
  9. 冬天来了,表情包都穿上了棉袄
  10. VMware不同处理器、硬盘设置——性能对比