问题解析
问题目标
小F得到了一个特殊的字符串,这个字符串只包含字符A、S、D、F,其长度总是4的倍数。他的任务是通过尽可能少的替换,使得A、S、D、F这四个字符在字符串中出现的频次相等。求出实现这一条件的最小子串长度。
测试样例
样例1:
输入:
input = "ADDF"
输出:1
样例2:
输入:
input = "ASAFASAFADDD"
输出:3
样例3:
输入:
input = "SSDDFFFFAAAS"
输出:1
样例4:
输入:
input = "AAAASSSSDDDDFFFF"
输出:0
样例5:
输入:
input = "AAAADDDDAAAASSSS"
输出:4
问题解析
-
频次平衡目标:
- 字符串长度为 nnn,所以每个字符(A、S、D、F)的目标频次为 target=n/4\text{target} = n / 4target=n/4。
-
滑动窗口方法:
- 使用滑动窗口确定一个子串,当替换这个子串内的字符后,剩余的字符能满足目标条件。
- 通过调整窗口的左右边界(
left和right),动态计算窗口内字符对整体字符频次的影响。
-
判断条件:
- 每次调整窗口后,判断去除窗口内的字符后,剩余字符的频次是否都小于等于目标频次。
实现思路
-
初始化:
- 使用
Counter统计字符串中每个字符的频次。 - 如果字符频次已满足平衡条件(即每个字符的频次等于
target),返回0。
- 使用
-
滑动窗口操作:
-
窗口移动:
- 从
right向右扩展窗口。 - 在每次扩展窗口后,更新
count,使窗口内的字符频次减少。
- 从
-
窗口收缩:
- 检查当前窗口外的字符是否满足平衡条件(每个字符的频次 ≤
target)。 - 如果满足,更新最小子串长度,尝试移动
left来收缩窗口。
- 检查当前窗口外的字符是否满足平衡条件(每个字符的频次 ≤
-
-
返回结果
代码实现
from collections import Counter
def solution(input):
n = len(input)
target = n // 4 # 每个字符的目标频次
count = Counter(input) # 统计字符频次
# 如果当前字符串已经平衡,直接返回0
if all(count[c] == target for c in "ASDF"):
return 0
min_len = n # 初始化最小子串长度为字符串长度
left = 0
# 右边界遍历字符串
for right in range(n):
count[input[right]] -= 1 # 窗口内右边界字符频次减1
# 窗口内外频次检查:窗口外的字符频次都 <= target
while all(count[c] <= target for c in "ASDF"):
min_len = min(min_len, right - left + 1) # 更新最小子串长度
count[input[left]] += 1 # 窗口内左边界字符频次恢复
left += 1 # 收缩窗口
return min_len
时间复杂度:
-
主要循环:
外层循环for right in range(n)遍历了整个字符串,每个字符最多被访问两次(一次通过right,一次通过left)。 -
内部循环:
内部的while循环在滑动窗口内收缩,最多也是 O(n) 次。因此,总的时间复杂度为 O(n) 。 -
综合时间复杂度:
O(n)+O(n)×O(1)=O(n)
空间复杂度:
Counter对象:
Counter使用了一个字典来存储字符频次。对于仅包含A、S、D、F的固定字符集,最多存储4个键值对。所以,空间复杂度是 O(1) 。
知识总结
-
频率统计:
- 统计字符串中目标字符的频率。
- 确定每个字符的理想频率,即
target = len(s) / 4。
-
滑动窗口:
- 使用滑动窗口高效地遍历字符串的所有可能子串。
- 调整窗口左右边界,动态找到满足条件的最小窗口。
-
窗口调整逻辑:
- 右扩: 确保窗口内覆盖到足够的字符。
- 左缩: 找到更小的满足条件的窗口,从而优化结果。
学习计划
- 每天设定固定的刷题数量,分级学习,逐渐掌握不同类型的题目,通过豆包AI题目记录,对错题解析,巩固易错点。通过题目解析,利用豆包AI的刷题功能,不断深挖算法,理解并去记忆其思路。
工具运用
-
结合豆包 AI 提供的解析,与在线教程进行交叉验证,巩固理解。利用算法书补充理论知识。
-
参与稀土掘金社区讨论,分享 AI 提供的解决方案和优化思路。