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

从美团春招真题‘区间删除’出发,聊聊如何用Python前缀和+二分查找搞定乘积末尾零问题

从美团春招真题看乘积末尾零问题:前缀和与二分查找的Python实战

在算法面试中,大厂常常会考察候选人对数学原理的理解和算法优化能力。美团春招真题中的"区间删除"问题就是一个典型例子——它表面上是一个数组操作问题,实际上考察的是对乘积末尾零生成机制的深入理解,以及如何将问题转化为高效的计算模型。

1. 问题本质与数学原理拆解

乘积末尾零的数量由乘数中2和5的因子对数决定。例如,数字20可以分解为2²×5¹,贡献了1对2和5的因子。因此,整个数组乘积的末尾零数量等于所有数字中2因子总数和5因子总数的较小值。

关键数学关系

  • 设原数组总2因子数为total2,总5因子数为total5
  • 删除区间中的2因子数为x2,5因子数为x5
  • 剩余部分的末尾零数需满足:min(total2-x2, total5-x5) >= k

这转化为两个不等式:

x2 <= total2 - k x5 <= total5 - k

实际案例验证: 以题目示例[2,5,3,4,20]为例:

  • 2的因子数:[1,0,0,2,2] → total2=5
  • 5的因子数:[0,1,0,0,1] → total5=2
  • 要求k=2,即需要min(5-x2,2-x5)>=2
  • 转化为x2<=3x5<=0

2. 暴力解法与常见误区分析

许多面试者首先想到的是暴力枚举所有可能的删除区间:

def count_zero_intervals(nums, k): factor2 = [count_factors(num, 2) for num in nums] factor5 = [count_factors(num, 5) for num in nums] total2, total5 = sum(factor2), sum(factor5) count = 0 n = len(nums) for i in range(n): x2 = x5 = 0 for j in range(i, n): x2 += factor2[j] x5 += factor5[j] if x2 <= (total2 - k) and x5 <= (total5 - k): count += 1 else: break return count

时间复杂度分析

  • 外层循环n次,内层平均n/2次 → O(n²)
  • 当n=1e5时,运算量达1e10次,完全不可接受

滑动窗口的陷阱: 有些候选人会尝试使用滑动窗口优化:

i = j = x2 = x5 = 0 while j < n: x2 += factor2[j] x5 += factor5[j] while not (x2 <= total2-k and x5 <= total5-k): x2 -= factor2[i] x5 -= factor5[i] i += 1 count += j-i+1 j += 1

这种方法的问题在于:

  1. 无法处理非连续有效区间的情况
  2. 会遗漏某些符合条件的子区间
  3. 当条件不满足时指针移动策略不明确

3. 前缀和+二分查找的优化方案

3.1 前缀和预处理

前缀和数组可以在O(1)时间内计算任意区间的因子和:

from itertools import accumulate prefix2 = [0] + list(accumulate(factor2)) prefix5 = [0] + list(accumulate(factor5))

这样,区间[i,j]的因子和为:

  • 2因子:prefix2[j+1] - prefix2[i]
  • 5因子:prefix5[j+1] - prefix5[i]

3.2 二分查找实现

对于每个左端点i,使用二分查找找到满足条件的最大右端点j:

from bisect import bisect_right def solve(): n, k = map(int, input().split()) nums = list(map(int, input().split())) factor2 = [count_factors(x, 2) for x in nums] factor5 = [count_factors(x, 5) for x in nums] total2, total5 = sum(factor2), sum(factor5) if min(total2, total5) < k: return 0 prefix2 = [0] + list(accumulate(factor2)) prefix5 = [0] + list(accumulate(factor5)) res = 0 for i in range(n+1): max_x2 = total2 - k max_x5 = total5 - k target2 = prefix2[i] + max_x2 target5 = prefix5[i] + max_x5 j2 = bisect_right(prefix2, target2) - 1 j5 = bisect_right(prefix5, target5) - 1 upper_bound = min(j2, j5) if upper_bound > i: res += upper_bound - i return res

复杂度分析

  • 预处理:O(n)
  • 二分查找:n次查找,每次O(logn)
  • 总复杂度:O(nlogn)

4. 实战技巧与边界处理

4.1 因子计数优化

def count_factors(x, factor): count = 0 while x > 0 and x % factor == 0: count += 1 x = x // factor return count

性能提示:对于大数,可以预先计算常见factor的幂次表加速运算

4.2 特殊情况处理

  1. k=0的情况:所有删除方案都满足条件
  2. total2或total5小于k:直接返回0
  3. 空数组处理:需要明确题目要求

4.3 测试用例设计

有效测试用例应包含:

  • 全2的倍数数组
  • 全5的倍数数组
  • 混合数组
  • 包含大素数的数组
  • 边界值(n=1, k=0等)

示例测试集:

test_cases = [ ([2,5,3,4,20], 2, 4), ([10,10,10], 3, 0), ([1,2,3,4,5], 0, 15), ([625]*1000, 4, 499500) ]

5. 同类问题扩展与思维训练

掌握这类问题的解决思路后,可以解决许多变种问题:

  1. 子数组乘积末尾零的最大数量:转化为寻找min(total2,total5)最大的子数组
  2. 必须保留区间的最少删除:转化为寻找满足条件的最长子数组
  3. 多维约束条件:如同时满足末尾零和区间和的条件

思维训练题: 给定数组,找到所有子数组,使得子数组乘积的末尾零数量等于整个数组乘积的末尾零数量。如何高效解决?

def special_subarrays(nums): total2 = sum(count_factors(x,2) for x in nums) total5 = sum(count_factors(x,5) for x in nums) target = min(total2, total5) prefix2 = [0]+list(accumulate(count_factors(x,2) for x in nums)) prefix5 = [0]+list(accumulate(count_factors(x,5) for x in nums)) from collections import defaultdict map2 = defaultdict(list) map5 = defaultdict(list) for i,val in enumerate(prefix2): map2[val].append(i) for i,val in enumerate(prefix5): map5[val].append(i) count = 0 for i in range(len(nums)+1): needed2 = prefix2[i] + (total2 - target) needed5 = prefix5[i] + (total5 - target) if needed2 in map2 and needed5 in map5: j2 = bisect_right(map2[needed2], i) j5 = bisect_right(map5[needed5], i) count += len(map2[needed2]) - j2 + len(map5[needed5]) - j5 return count

在实际面试中,遇到类似问题时应首先分析问题背后的数学本质,然后考虑如何将数学条件转化为程序可计算的形式,最后选择合适的数据结构进行优化。前缀和与二分查找的组合在区间统计类问题中非常常见,值得深入掌握。

http://www.zskr.cn/news/1444570.html

相关文章:

  • READ COMMITTED(读已提交)是数据库事务的四种标准隔离级别之一(其余为:READ UNCOMMITTED、REPEATABLE READ、SERIALIZABLE)
  • 解锁虚拟化边界:深度解析VMware macOS解锁器的核心技术原理与实践
  • 从BMP文件头到像素遍历:手把手教你用C语言和VS2022解析一张图片的完整数据
  • 为机器学习项目设计专用编程语言:从Python痛点看未来ML工程范式
  • 别再乱放了!Android14编译时,如何精准控制你的模块输出到system、vendor还是product分区?
  • 告别手写公式烦恼:三个免费在线工具,截图/手写一键转LaTeX(附保姆级教程)
  • 为什么92%的用户删不干净Sora 2水印?深度逆向其v2.1.3水印注入协议,附Python自动化剥离脚本
  • 从矩阵求和到状态更新:图解Blelloch并行扫描如何成为Mamba.py的‘加速引擎’
  • 用Python和YOLOv5给DNF写个自动刷图脚本:从截图到驱动级按键的完整流程
  • Android14编译实战:手把手教你配置Android.bp,让模块精准输出到system/product/vendor/odm分区
  • 无人机数据处理避坑指南:用C++和Eigen库搞定摄影测量中的欧拉角转换(附完整代码)
  • 玻璃钢水箱的价格是多少,语琪玻璃钢的呢? - 工业推荐榜
  • 在TCP三次握手过程中,“第二次握手”是指服务器对客户端发起的连接请求作出响应的步骤
  • 从一篇Nature文章看MetaQTL:如何用它发现小麦抗病基因的‘黄金位点’?
  • 保姆级图解:GDDR6的Clamshell模式到底怎么玩?PCB布线避坑指南
  • 激活稀疏化技术:提升LLM推理效率的动态压缩方案
  • 避坑指南:UE5多语言游戏打包后语言失效?检查这3个配置(含控制器设置)
  • 别再傻傻手动拼接SQL了!用Hackbar插件(Firefox版)一键生成Payload,效率翻倍
  • 别再被蓝牙授权卡住了!微信小程序iOS/Android双端完整避坑指南(附Taro代码)
  • 从意图识别到响应生成:构建智能对话系统的核心技术与实践
  • 插画课程口碑好的有哪些? - 工业推荐榜
  • 保姆级教程:用Qt和MQTT把数据发到阿里云物联网平台(附完整C代码)
  • 春秋云镜——CVE-2020-25540
  • 从0到1:我是如何设计大模型结构化输出系统的
  • 千问 LeetCode 2926. 平衡子序列的最大和 C++实现
  • Simulink不连续模块组实战:用Saturation和DeadZone搞定汽车控制器的信号处理(2021b版)
  • 避坑指南:用ArcGIS统计格网耕地比例时,FID连接和创建唯一ID到底哪个更靠谱?
  • 别再为精度发愁了!用OpenFHE的Meta-BTS迭代自举,轻松实现CKKS高精度计算
  • AI赋能者:从专用智能到人机协同的未来
  • 2026年RFID采集器口碑与选购指南 - myqiye