学习笔记:统计数组中符合条件的排列子序列
题目解析
这道题目让我从数组的排列和子序列的角度重新理解了“排列”的定义:
子序列: 数组中删除若干元素(或不删除)后形成的新数组。 排列: 从1到m,每个数字必须出现且只能出现一次,并且子序列按升序排列。 题目的核心是计算数组中有多少个子序列满足排列的要求。比如,对数组 [1, 1, 5, 2, 3, 4]: 我们可以找到多个排列子序列,例如 [1, 2, 3, 4]。
分析与思路
1. 题目核心
首先,我们需要理解两点: 要构成一个合法的排列,数字 1,2,...,m 必须依次完整地出现在子序列中。 数字 i 在原数组中出现的次数可以影响排列子序列的数量。例如,数字 1 出现两次时,排列子序列可能会倍增。
2. 解题思路
统计元素频率: 用一个哈希表记录数组中每个数字的出现次数。 按顺序构造排列: 从数字1开始检查,如果缺少某个数字,排列无法继续。 计算排列数量: 假设当前数字是 i,其出现次数为 cnt[i],则当前排列子序列的数量乘以 cnt[i]。 结果累加: 将每一轮构成的排列子序列数量累加到答案中。
3. 数学分析
为了处理大数运算,题目要求我们对结果取模 10^9+7,这在很多竞赛中都很常见。
代码实现
以下是完整代码,并附详细注释:
mod = 10**9 + 7 # 模数
mp = {} # 记录每个数字的出现次数
ans = 0 # 最终结果
# 统计每个数字的频率
for x in a:
if x in mp:
mp[x] += 1
else:
mp[x] = 1
res = 1 # 当前排列数量
for i in range(1, n + 1):
if i not in mp: # 如果缺少某个数字,无法构成排列
break
res = (res * mp[i]) % mod # 当前排列数量
ans = (ans + res) % mod # 累加到总结果中
return ans
测试用例
为了验证代码正确性,需要使用以下测试用例:
# 示例测试用例
print(solution(6, [1, 1, 5, 2, 3, 4]) == 10) # 子序列有10种排列
print(solution(5, [1, 2, 3, 4, 5]) == 5) # 子序列有5种排列
print(solution(7, [1, 3, 5, 2, 4, 6, 7]) == 7) # 子序列有7种排列
# 边界测试用例
print(solution(3, [1, 1, 1]) == 1) # 只有一个合法排列
print(solution(4, [1, 2, 3, 3, 3, 4]) == 4) # 子序列有4种排列
学习总结
1. 题目理解难点
这道题的关键是理解“排列子序列”的定义。子序列不需要连续,但必须包含从1到某个数字m的所有数字。并且数组中的重复数字会影响排列数量,这需要特别注意。
2. 我的学习心得
频率统计: 通过哈希表统计每个数字的出现次数,这是处理类似问题的常用方法。 动态累加: 在循环中,每次计算当前数字能增加的排列数量,并累加到最终结果中。这种逐步构造答案的方法很适合处理复杂问题。 取模处理: 模运算是一种防止溢出并保证结果正确性的重要技巧,尤其是在处理大数时非常实用。
3. 对初学者的建议
分步分析: 面对复杂问题时,先分解问题,比如先处理子序列的生成,再考虑排列的定义。 多写测试用例: 测试是验证算法的最佳手段。自己动手设计测试数据,并分析结果是否符合预期。 理解数学规律: 在解决问题时,多关注排列的数学关系,比如本题中频率乘积的规律。
学习计划
为了更高效地学习和掌握类似的问题,我制定了以下计划:
1. 明确基础知识
学习子序列和排列的定义。 掌握基本的数据结构操作,如哈希表的使用以及数组的遍历。
2. 针对性刷题
使用豆包MarsCode AI题库,先从子序列相关的简单题目入手,例如: 找到数组中所有子序列的数量。 验证某个子序列是否满足特定条件。 逐步提升到中等难度的排列问题,通过拆解题目理解题意。
3. 巩固与复习
错题分析: 将做错的题目分类整理,比如“遗漏边界情况”或“数字频率统计错误”。 总结规律: 比如如何通过频率统计减少冗余计算。
4. 定期复盘
每周总结学习收获,将学到的算法思想用自己的话写成笔记。 定期复习错题,确保不在同类问题上重复出错。