题目解析
题目描述: 给定一个长度为 N 的数组 A,编号从 0 到 N-1。如果一个从索引 i 到 j 的子数组包含一个出现至少 K 次的元素 X,我们就称这个子数组为“疯狂子数组”。现在,你需要计算数组 A 中所有的疯狂子数组的数量。
思路:
- 滑动窗口:使用滑动窗口技术来高效地计算所有“疯狂子数组”的数量。
- 计数器:使用一个计数器
count来记录当前窗口内每个元素的出现次数。 - 左右指针:使用两个指针
left和right来表示滑动窗口的左右边界。 - 结果计数:使用一个变量
result来记录所有“疯狂子数组”的数量。 - 有效性标志:使用一个布尔变量
valid来标记当前窗口内是否有元素出现次数达到或超过K。
图解: 假设数组 A = [1, 5, 2, 5, 2],K = 2。
-
初始状态:
left = 0,right = 0,count = {1: 1},valid = False,result = 0 -
右指针移动到
right = 1:count = {1: 1, 5: 1},valid = False -
右指针移动到
right = 2:count = {1: 1, 5: 1, 2: 1},valid = False -
右指针移动到
right = 3:count = {1: 1, 5: 2, 2: 1},valid = True- 计算以
right = 3为结尾的所有子数组数量:result += (N - right) = 5 - 3 = 2
- 计算以
-
左指针移动到
left = 1:count = {1: 1, 5: 1, 2: 1},valid = False -
右指针移动到
right = 4:count = {1: 1, 5: 1, 2: 2},valid = True- 计算以
right = 4为结尾的所有子数组数量:result += (N - right) = 5 - 4 = 1
- 计算以
-
左指针移动到
left = 2:count = {1: 1, 5: 1, 2: 1},valid = False -
右指针移动到
right = 5:count = {1: 1, 5: 1, 2: 2},valid = True- 计算以
right = 5为结尾的所有子数组数量:result += (N - right) = 5 - 5 = 1
- 计算以
-
左指针移动到
left = 3:count = {1: 1, 5: 1, 2: 1},valid = False -
右指针移动到
right = 6:count = {1: 1, 5: 1, 2: 2},valid = True- 计算以
right = 6为结尾的所有子数组数量:result += (N - right) = 5 - 6 = 0
- 计算以
最终结果:result = 5
代码详解:
python
def solution(N: int, K: int, A: list) -> int:
from collections import defaultdict
count = defaultdict(int)
left = 0
result = 0
valid = False
for right in range(N):
count[A[right]] += 1
if count[A[right]] == K:
valid = True
while valid:
result += (N - right)
count[A[left]] -= 1
if count[A[left]] == K - 1:
valid = False
if count[A[left]] == 0:
del count[A[left]]
left += 1
return result
if __name__ == '__main__':
print(solution(N=5, K=2, A=[1, 5, 2, 5, 2]) == 5)
print(solution(N=6, K=3, A=[1, 1, 2, 2, 1, 2]) == 4)
print(solution(N=4, K=2, A=[4, 4, 4, 4]) == 6)
知识总结
新知识点:
- 滑动窗口技术:滑动窗口是一种常用的算法技巧,适用于处理数组或字符串中的子数组或子串问题。通过维护一个窗口,可以在 O(N) 时间复杂度内解决问题。
- 计数器:使用
defaultdict来记录当前窗口内每个元素的出现次数,方便快速增删和查询。 - 布尔标志:使用布尔变量
valid来标记当前窗口内是否有元素出现次数达到或超过K,简化逻辑判断。
学习建议:
- 理解滑动窗口:滑动窗口技术的核心在于维护一个窗口,通过移动左右指针来调整窗口的大小。理解这一点后,可以解决很多子数组或子串问题。
- 熟练使用计数器:
defaultdict是 Python 中非常实用的数据结构,可以方便地进行计数操作。多练习使用defaultdict,可以提高代码的简洁性和效率。 - 调试和测试:在编写代码时,多进行调试和测试,确保每个部分的逻辑都正确无误。可以使用简单的测试用例来验证代码的正确性。
问题总结
出现问题的原因:
- 逻辑错误:在第一次实现中,计算子数组数量的逻辑有误,导致结果不正确。
- 边界条件处理不当:在第二次实现中,移动左边界时没有正确处理边界条件,导致部分子数组没有被正确计算。
解决方法:
- 仔细检查逻辑:在编写代码时,仔细检查每个部分的逻辑,确保没有遗漏或错误。
- 增加测试用例:编写更多的测试用例,特别是边界条件和特殊情况,确保代码的鲁棒性。
- 调试工具:使用调试工具(如 PyCharm 的调试功能)来逐步执行代码,观察变量的变化,找出问题所在。
可能出现问题的原因及解决办法:
-
数据结构选择不当:选择合适的数据结构非常重要,不同的数据结构适合不同的场景。如果选择不当,可能导致代码复杂度增加或性能下降。
- 解决办法:在选择数据结构时,考虑问题的具体需求,选择最合适的结构。
-
代码冗余:代码中可能存在冗余的部分,影响可读性和性能。
- 解决办法:定期重构代码,去除冗余部分,提高代码的简洁性和效率。
-
边界条件处理:边界条件是容易出错的地方,需要特别注意。
- 解决办法:在编写代码时,仔细考虑边界条件,并编写相应的测试用例进行验证。
通过以上总结和反思,希望能够在未来的编程实践中避免类似的问题,提高代码的质量和效率。