)
科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载导读本文基于算法竞赛模板库 codeforces-go 中 leetcode/weekly/315/d/README.md 的官方题解深入讲解 LeetCode 第 315 场周赛 D 题2444. 统计定界子数组的数目的线性解法。核心方法是从特殊到一般先剖析所有元素均落在[minK, maxK]内的简化模型再引入范围外最近元素哨兵位置将问题收敛为一次遍历中实时维护三个下标并累加贡献的 O(n) 算法。读完本文你将掌握枚举子数组右端点、统计合法左端点这一通用贡献法套路并能将其迁移到区间计数类问题的求解中。一、题目与题解在仓库中的位置本题解收录于本仓库的周赛题解目录中对应的仓库结构如下题解文档leetcode/weekly/315/d/README.mdGo 参考实现leetcode/weekly/315/d/d.go单元测试leetcode/weekly/315/d/d_test.go测试用例数据leetcode/weekly/315/d/d.txt题意可概括为给定整数数组nums以及下界minK、上界maxK统计所有满足下述条件的子数组数目子数组的最小值等于minK子数组的最大值等于maxK。一个朴素的做法是枚举全部 O(n²) 个子数组并逐一检查但题目数据规模要求线性解法。原题解给出的思路正是从特殊到一般这也是本篇文章展开的主线。二、从特殊到一般先攻克简化模型原题解开篇即点明方法论——先考虑一个简单的情况nums的所有元素都在[minK, maxK]范围内。在这种理想情形下任意子数组的最小值天然不小于minK、最大值天然不大于maxK于是原问题等价于同时包含minK和maxK的子数组的个数。核心思路枚举右端点统计合法左端点遍历nums动态维护两个关键下标minIminK最近一次出现的位置maxImaxK最近一次出现的位置。当遍历到nums[i]时如果minK和maxK都曾出现过则所有左端点落在[0, min(minI, maxI)]的子数组都同时包含minK与maxK。这是因为右端点固定为i只要左端点不超过min(minI, maxI)区间[left, i]就必然覆盖了最近的那个minK和最近的那个maxK由于所有元素都在范围内同时包含minK和maxK的子数组其最小值一定是minK、最大值一定是maxK。因此以i为右端点的合法子数组个数为$$ \min(\textit{minI},\textit{maxI})1 $$把每个右端点对应的贡献累加起来就是简化模型的答案。手动推演一个例子以题解给出的图解数据nums [1, 4, 3, 4, 2, 2, 3, 3]对应minK 2, maxK 4为例前四个元素中4出现两次下标 1、3minK 2到下标 4 才首次出现遍历到i 4值2时minI 4、maxI 3贡献min(4, 3) 1 4即左端点可取0..3遍历到i 5值2时minI 5、maxI 3贡献min(5, 3) 1 4遍历到i 6值3时minI 5、maxI 6贡献min(5, 6) 1 6左端点可取0..5。注意元素1位于[2, 4]之外属于越界元素因此在一般模型中需要额外处理见下一节。三、回到原问题引入范围外哨兵i0真实数据中nums必然存在落在[minK, maxK]之外的元素而合法子数组不能包含任何越界元素。若不做处理上面的计数会把跨过越界元素的子数组也算进去造成多算。原题解的解法是额外维护一个下标i0在[minK, maxK]范围之外的最近元素位置。有了i0之后以i为右端点时左端点必须在i0右侧即左端点取值范围收缩为$$ [\textit{i}_01,\ \min(\textit{minI},\textit{maxI})] $$于是一般情形下以i为右端点的合法子数组个数为$$ \min(\textit{minI},\textit{maxI})-\textit{i}_0 $$结合上述nums [1, 4, 3, 4, 2, 2, 3, 3]的例子下标0处的元素1越界i0 0。则i 4时贡献变为min(4, 3) - 0 3即左端点只能取1..3排除了越界元素1所在的子数组左端点 0i 6时贡献变为min(5, 6) - 0 5左端点取1..5。可见i0相当于一道禁止越界的闸门把跨越越界元素的子数组全部排除在外。实现时的两个工程细节原题解特别指出代码实现时需要注意的两点初始化哨兵为-1令minI maxI i0 -1天然兼容尚未找到相应元素的情况——在minK或maxK出现之前min(minI, maxI)为-1min(minI, maxI) - i0为负或零贡献不会为正。对贡献取max(..., 0)下限保护若min(minI, maxI) - i0 0说明在i0右侧minK和maxK尚未同时出现此时以i为右端点的合法子数组个数为0。因此统一写成$$ \max\big(\min(\textit{minI},\textit{maxI})-\textit{i}_0,\ 0\big) $$避免向答案累加负数。四、多语言代码实现原题解给出了 Python、Java、C、C、Go、JavaScript、Rust 共七种语言的完整实现核心逻辑完全一致单次遍历、三个下标、O(1) 辅助空间。以下全部原样收录便于跨语言对比与直接复用。class Solution: def countSubarrays(self, nums: List[int], minK: int, maxK: int) - int: ans 0 min_i max_i i0 -1 for i, x in enumerate(nums): if x minK: min_i i # 最近的 minK 位置 if x maxK: max_i i # 最近的 maxK 位置 if not minK x maxK: i0 i # 子数组不能包含 nums[i0] ans max(min(min_i, max_i) - i0, 0) return ansclass Solution: def countSubarrays(self, nums: List[int], minK: int, maxK: int) - int: ans 0 min_i max_i i0 -1 for i, x in enumerate(nums): if x minK: min_i i if x maxK: max_i i if not minK x maxK: i0 i j min_i if min_i max_i else max_i if j i0: ans j - i0 return ansclass Solution { public long countSubarrays(int[] nums, int minK, int maxK) { long ans 0; int minI -1, maxI -1, i0 -1; for (int i 0; i nums.length; i) { int x nums[i]; if (x minK) { minI i; // 最近的 minK 位置 } if (x maxK) { maxI i; // 最近的 maxK 位置 } if (x minK || x maxK) { i0 i; // 子数组不能包含 nums[i0] } ans Math.max(Math.min(minI, maxI) - i0, 0); } return ans; } }class Solution { public: long long countSubarrays(vectorint nums, int minK, int maxK) { long long ans 0; int min_i -1, max_i -1, i0 -1; for (int i 0; i nums.size(); i) { int x nums[i]; if (x minK) { min_i i; // 最近的 minK 位置 } if (x maxK) { max_i i; // 最近的 maxK 位置 } if (x minK || x maxK) { i0 i; // 子数组不能包含 nums[i0] } ans max(min(min_i, max_i) - i0, 0); } return ans; } };#define MIN(a, b) ((b) (a) ? (b) : (a)) #define MAX(a, b) ((b) (a) ? (b) : (a)) long long countSubarrays(int* nums, int numsSize, int minK, int maxK) { long long ans 0; int min_i -1, max_i -1, i0 -1; for (int i 0; i numsSize; i) { int x nums[i]; if (x minK) { min_i i; // 最近的 minK 位置 } if (x maxK) { max_i i; // 最近的 maxK 位置 } if (x minK || x maxK) { i0 i; // 子数组不能包含 nums[i0] } ans MAX(MIN(min_i, max_i) - i0, 0); } return ans; }func countSubarrays(nums []int, minK, maxK int) (ans int64) { minI, maxI, i0 : -1, -1, -1 for i, x : range nums { if x minK { minI i // 最近的 minK 位置 } if x maxK { maxI i // 最近的 maxK 位置 } if x minK || x maxK { i0 i // 子数组不能包含 nums[i0] } ans int64(max(min(minI, maxI)-i0, 0)) } return }var countSubarrays function(nums, minK, maxK) { let ans 0, minI -1, maxI -1, i0 -1; for (let i 0; i nums.length; i) { const x nums[i]; if (x minK) { minI i; // 最近的 minK 位置 } if (x maxK) { maxI i; // 最近的 maxK 位置 } if (x minK || x maxK) { i0 i; // 子数组不能包含 nums[i0] } ans Math.max(Math.min(minI, maxI) - i0, 0); } return ans; };impl Solution { pub fn count_subarrays(nums: Veci32, min_k: i32, max_k: i32) - i64 { let mut ans 0; let mut min_i -1; let mut max_i -1; let mut i0 -1; for (i, x) in nums.into_iter().enumerate() { let i i as i32; if x min_k { min_i i; // 最近的 min_k 位置 } if x max_k { max_i i; // 最近的 max_k 位置 } if x min_k || x max_k { i0 i; // 子数组不能包含 nums[i0] } ans 0.max(min_i.min(max_i) - i0) as i64; } ans } }其中Go 版本的写法与仓库中的参考实现完全一致注意返回值类型为int64Java 为long、C 为long long、Rust 为i64这是为了防止大数组下答案溢出 32 位整型。复杂度分析时间复杂度O(n)其中 n 为nums的长度。每个元素仅被遍历一次每次迭代只做常数次比较与加法。空间复杂度O(1)仅使用minI、maxI、i0与ans四个变量。五、仓库源码与测试佐证1. Go 参考实现仓库中的 d.go 与题解文档中的 Go 代码一一对应同样维护minI / maxI / i0三个下标并在循环内执行ans int64(max(min(minI, maxI)-i0, 0))最后通过命名返回值直接return。文件末尾还内联了min/max两个工具函数在 Go 1.21 内置min/max之前的常见写法。2. 测试驱动与用例数据该题解配套了完整的测试链路d_test.go 通过testutil.RunLeetCodeFuncWithFile(t, countSubarrays, d.txt, targetCaseNum)驱动测试测试函数签名直接指向countSubarrays测试用例数据保存在 d.txt 中采用每 3 行一组的格式函数有 2 个输入参数 1 个返回值共两组用例nums [1,3,5,2,7,5]、minK 1、maxK 5→ 答案2越界元素7把数组切分为两段合法子数组为[1,3,5]与[1,3,5,2]nums [1,1,1,1]、minK 1、maxK 1→ 答案10全部元素合法任意子数组都满足条件即 4×5/2 10。3. 测试工具的实现原理测试框架 leetcode/testutil/leetcode.go 中的RunLeetCodeFuncWithFile会读取用例文件按函数的参数个数 返回值个数fNumIn fNumOut切分行数据再反射调用被测函数并逐例断言。这意味着只要照着题解把函数实现补全即可通过仓库自带的用例数据一键验证正确性也可以在此基础上自行追加用例。六、相似题目与迁移要点原题解末尾将本题归入滑动窗口与双指针 / 区间计数一类并列出相似题目795. 区间子数组个数统计最大值位于[left, right]的子数组数量同样是利用枚举右端点、维护边界下标、累加左端点个数的套路与本题的i0闸门思想同源。这一类题目的通用迁移要点可以总结为先化简再还原先假设数据无干扰建立计数公式再引入非法元素最近位置作为哨兵把公式修正到真实数据上枚举右端点、统计左端点把数子数组转化为对每个右端点求合法左端点区间长度避免 O(n²) 枚举哨兵初值-1 贡献下限max(..., 0)保证未出现所需元素时不产生负贡献是代码可读性与正确性的关键。七、小结本文完整复现了 leetcode/weekly/315/d/README.md 的核心思路通过从特殊到一般的递进推导将统计定界子数组转化为单次遍历下对minI、maxI、i0三个下标的维护与贡献累加最终得到 O(n) 时间、O(1) 空间的解法并配套给出了七种主流语言的实现与仓库内可运行的测试链路。掌握枚举右端点、统计合法左端点的贡献法即可举一反三地处理一类区间计数问题。赞分享科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载相关推荐codeforces-go 仓库题解LeetCode 2364 统计坏数对的数目——正难则反 枚举右维护左codeforces go 仓库题解LeetCode 2364 统计坏数对的数目——正难则反 枚举右维护左 本篇文章以 leetcode/biweekly科学计算一条命令免费激活 Win11MAS 激活脚本四种免密钥方法Windows 与 Office 一次搞定一条命令免费激活 Win11MAS 激活脚本四种免密钥方法Windows 与 Office 一次搞定 MAS 激活脚本是面向 Win11 免费激活与 Off操作系统LeetCode 1291《顺次数》三种解法详解从子串枚举到定长滑动窗口附 codeforces-go 仓库 Go 实现与测试验证LeetCode 1291《顺次数》三种解法详解从子串枚举到定长滑动窗口附 codeforces go 仓库 Go 实现与测试验证 本文以算法竞赛模板库科学计算上一篇WaveTools终极指南如何高效优化《鸣潮》性能与抽卡分析下一篇Delta模拟器金手指实战教程从首次启用到自定义代码的完整攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考