当前位置: 首页 > news >正文

LeetCode 1248:统计「优美子数组」 | 前缀和与奇数计数

LeetCode 1248:统计「优美子数组」 | 前缀和与奇数计数

引言

统计「优美子数组」(Count Number of Nice Subarrays)是 LeetCode 第 1248 题,难度为 Medium。题目要求找出数组中恰好有 K 个奇数的连续子数组的数量。这道题是前缀和与哈希表结合的又一个经典案例,展示了如何将"恰好 K 个奇数"的问题转化为"两个前缀和的差为 K"的问题。

这个问题的变种包括:恰好 K 个偶数的子数组、至少 K 个奇数的子数组等。核心思路都是将子数组的某种属性(前缀和)存储在哈希表中,然后查找满足条件的配对。

问题分析

题目描述

给定一个整数数组 nums 和一个整数 K,如果一个子数组中恰好有 K 个奇数,则称其为"优美子数组"。返回优美子数组的数量。

例如,输入 nums = [1,1,2,1,1,3],K = 3,有 5 个优美子数组:

  • [1,1,2,1,1](索引 0-4)
  • [1,2,1,1,3](索引 1-5)
  • [1,1,2,1](索引 0-3)
  • [2,1,1,3](索引 2-5)
  • [1,2,1,1](索引 1-4)

问题特点

这道题的核心挑战是如何高效地统计"恰好有 K 个奇数"的子数组数量。如果使用暴力枚举所有子数组,时间复杂度为 O(n²)。通过将问题转化为前缀和的差,我们可以使用哈希表在 O(n) 时间内解决。

将数组中的奇数映射为 1,偶数映射为 0,则子数组中奇数的数量等于该子数组对应元素的和。因此,"恰好有 K 个奇数"等价于"对应元素和为 K"。

前缀和解法

转换思路

将数组转换为:对于每个元素,如果是奇数则为 1,如果是偶数则为 0。这样,任意子数组的元素和就是该子数组中奇数的个数。

def numberOfSubarrays(nums, k): prefix_sum = 0 count = 0 prefix_map = {0: 1} for num in nums: prefix_sum += num % 2 count += prefix_map.get(prefix_sum - k, 0) prefix_map[prefix_sum] = prefix_map.get(prefix_sum, 0) + 1 return count

这里 num % 2 将奇数转换为 1,偶数转换为 0。prefix_map 存储每个前缀和出现的次数,初始化为 {0: 1} 表示空前缀和。

详细解析

在遍历数组时,prefix_sum 累积当前前缀和(奇数计数)。对于每个位置,prefix_sum - k 就是我们需要找的之前的前缀和。如果存在前缀和等于 prefix_sum - k 的位置,说明从那个位置到当前位置的子数组恰好有 K 个奇数。

prefix_map[prefix_sum - k] 给出了满足条件的之前位置的数量,将这个值加到 count 上,就统计了所有以当前位置结尾的优美子数组的数量。

算法正确性证明

关键引理

对于任意位置 i 和 j(j < i),子数组 nums[j+1:i] 中奇数的个数等于 prefix_sum[i] - prefix_sum[j],其中 prefix_sum 是奇数计数的前缀和。

这个引理成立是因为 prefix_sum[i] = 奇数(nums[0:i]),prefix_sum[j] = 奇数(nums[0:j]),两者相减得到奇数(nums[j+1:i])。

正确性分析

对于每个位置 i,我们希望统计所有满足 prefix_sum[i] - prefix_sum[j] = K 的 j。对于固定的 i,满足条件的 j 就是所有使得 prefix_sum[j] = prefix_sum[i] - K 的位置。

通过在哈希表中查找 prefix_sum[i] - K,我们得到了所有满足条件的 j 的数量。将这个数量累加到 count 上,就正确统计了所有优美子数组的数量。

复杂度分析

时间复杂度

时间复杂度为 O(n),因为只遍历数组一次,每次迭代进行常数次哈希表操作。

空间复杂度

空间复杂度为 O(n),在最坏情况下哈希表需要存储 n+1 个不同的前缀和。

代码实现

Python 实现

def numberOfSubarrays(nums, k): prefix_sum = 0 count = 0 prefix_map = {0: 1} for num in nums: prefix_sum += num % 2 count += prefix_map.get(prefix_sum - k, 0) prefix_map[prefix_sum] = prefix_map.get(prefix_sum, 0) + 1 return count

Java 实现

public int numberOfSubarrays(int[] nums, int k) { int prefixSum = 0; int count = 0; Map<Integer, Integer> prefixMap = new HashMap<>(); prefixMap.put(0, 1); for (int num : nums) { prefixSum += num & 1; count += prefixMap.getOrDefault(prefixSum - k, 0); prefixMap.put(prefixSum, prefixMap.getOrDefault(prefixSum, 0) + 1); } return count; }

变种问题

恰好 K 个偶数

将 num % 2 改为 (num + 1) % 2 或 (num % 2) ^ 1,将偶数转换为 1,奇数转换为 0。

def numberOfSubarraysEven(nums, k): prefix_sum = 0 count = 0 prefix_map = {0: 1} for num in nums: prefix_sum += (num + 1) % 2 count += prefix_map.get(prefix_sum - k, 0) prefix_map[prefix_sum] = prefix_map.get(prefix_sum, 0) + 1 return count

最多 K 个奇数

最多 K 个奇数的子数组数量等于"恰好 0 个奇数" + "恰好 1 个奇数" + ... + "恰好 K 个奇数"。可以遍历所有情况计算。

def atMostKSubarrays(nums, k): prefix_sum = 0 count = 0 prefix_map = {0: 1} for num in nums: prefix_sum += num % 2 count += prefix_map.get(prefix_sum - k, 0) prefix_map[prefix_sum] = prefix_map.get(prefix_sum, 0) + 1 return count

实际上,最多 K 个奇数的优美子数组就是"恰好 0 个奇数" + "恰好 1 个奇数" + ... + "恰好 K 个奇数",可以通过累加计算。

至少 K 个奇数

至少 K 个奇数的子数组数量等于总数减去最多 K-1 个奇数的子数组数量。

测试用例

def test_number_of_subarrays(): assert numberOfSubarrays([1,1,2,1,1,3], 3) == 5 assert numberOfSubarrays([2,2,2,2,2], 0) == 10 assert numberOfSubarrays([1,1,1,1], 2) == 3 assert numberOfSubarrays([1,2,3], 1) == 2 assert numberOfSubarrays([], 0) == 1 assert numberOfSubarrays([2,2,2,1,2], 2) == 3 print("所有测试用例通过!")

实际应用场景

统计恰好有 K 个某种特征的元素问题在现实中有很多应用。在数据分析中,可以统计恰好有 K 个异常值的数据段。在信号处理中,可以统计恰好有 K 个峰值的数据段。在金融分析中,可以统计交易天数中恰好有 K 个上涨日的周期。

前缀和与哈希表结合的方法是处理"恰好 K 个"统计问题的通用范式。

总结

统计优美子数组问题展示了前缀和与哈希表结合的强大能力。通过将奇数映射为 1、偶数映射为 0,原问题转化为前缀和的差问题。哈希表存储每个前缀和出现的次数,使得我们可以 O(1) 时间内查找满足条件的配对。

这个问题的关键洞察是:子数组的某种属性和 = 两个前缀和的差。通过哈希表快速查找差为 K 的两个前缀和,我们可以高效地统计所有满足条件的子数组。希望通过本文的讲解,读者能够掌握这种将问题转化的思想,并将其应用到更多类似问题的解决中。

http://www.rkmt.cn/news/1363356.html

相关文章:

  • 如何3步完成硬件适配:终极自动化配置指南
  • APS与RAPS:置信预测中覆盖保证与集合效率的权衡解析
  • 鸿蒙electron跨端框架PC墨案写作实战:把 Markdown 正文区做成桌面写作的中心
  • 大语言模型在临床试验预测与优化中的应用与挑战
  • Vision Mamba边缘部署:从算法瓶颈到专用硬件加速器设计
  • 2026年4月商用中央空调直销厂家口碑推荐,口碑好的商用中央空调哪家好,空气循环,保持室内空气新鲜 - 品牌推荐师
  • LeetCode 523:连续的子数组和 | 前缀和同余定理
  • LeetCode 238:除自身以外数组的乘积 | 前缀积与后缀积
  • SMGI框架:通用人工智能的结构元模型与实现路径解析
  • Win10/Win11频繁蓝屏DPC_WATCHDOG_VIOLATION?别慌,用WinDBG的!dpcwatchdog命令5分钟定位元凶
  • Autumn Valley资源包:开放世界性能优化实战指南
  • 基于FeFET的动态可重构FPGA:实现亚纳秒级上下文切换的硬件加速新架构
  • Burp Suite扫描深度配置指南:被动扫描、主动扫描与自定义插入点协同调优
  • Unity第一人称射击骨架:视角稳定、帧级响应与物理化弹道实现
  • CAD+MLIP:高效计算固体振动自由能与热力学性质的技术实践
  • 统信UOS/麒麟KYLINOS系统管理员必备:一键脚本批量清除所有用户的数科OFD阅读历史
  • 除了Easy App Locker,还有哪些Mac应用加锁方案?横向对比与避坑指南
  • Unity PBR材质工作流:800个开箱即用的工业级材质球
  • ARCADE:用AR任务驱动评估,弥合CV模型指标与真实感知的鸿沟
  • Arm Fast Models 11.31版本更新与实战指南
  • 解决SELinux下ARM DS-5文本重定位权限问题
  • 从零到一:用 LangChain 搭建你的第一个 AI Agent,让 LLM 自己干活!
  • 最后一公里交付失控?AI Agent+IoT+数字孪生闭环正在重构LSP技术栈——3家上市物流科技公司CTO联合预警
  • 计算机视觉模型失败模式自动化发现与自然语言描述技术详解
  • SEO数据管道:用Airflow搭建自动化工作流
  • MCB251开发板P1.0引脚功能与RS232接口选择解析
  • 用格拉姆矩阵特征值调整替代SVD,高效求解带正交约束的优化问题
  • Keil µVision多平台开发:Project Targets实战指南
  • FreeTacMan触觉感知系统:机器人操作的数据采集革命
  • Cortex-R82集成ELA-600调试模块的信号连接问题解析