今天刷的也是一道难度中等的题目:最小替换子串长度
问题描述
小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
为了将给定的字符串通过最少的替换使得字符 A、S、D、F 的出现频次相等,我们需要解决以下问题:
思路与分析
-
问题理解:
-
题目要求你将字符串中的字符
A、S、D、F的出现频率调整到相等。假设字符串的长度是n,因为每次替换的目标是让这四个字符的出现频次相等,我们可以推算出:- 目标是每个字符出现的次数应该为
n / 4(这里n一定是4的倍数)。
- 目标是每个字符出现的次数应该为
-
-
步骤概括:
- 统计字符串中每个字符
A、S、D、F的频次。 - 计算每个字符的超额和缺失。
- 通过滑动窗口(双指针)找到最小的子串,使得可以通过替换操作使字符频次平衡。
- 统计字符串中每个字符
-
关键步骤的分析:
- 统计频次:首先统计每个字符在字符串中的出现次数。这一步是简单的计数操作,时间复杂度是
O(n)。 - 计算目标频次:每个字符的目标频次是
n / 4。然后通过比较当前字符的频次与目标频次的差距,得到超额或缺失。超额是指某个字符的出现次数超过了目标频次,缺失是指某个字符的出现次数低于目标频次。 - 滑动窗口法:接下来,利用滑动窗口法来查找最小的子串,使得我们可以通过替换字符来平衡频次。滑动窗口法的思想是通过两个指针来维护一个区间,该区间包含足够的多余字符,可以通过替换使频次达到目标。
- 统计频次:首先统计每个字符在字符串中的出现次数。这一步是简单的计数操作,时间复杂度是
-
核心操作:
- 滑动窗口的扩展:每次向右移动右指针,加入新的字符。更新当前窗口中每个字符的计数。
- 滑动窗口的收缩:当当前窗口满足平衡条件(即超额字符总数已经可以通过替换来平衡),尝试缩小窗口,从左边开始移动左指针,看看是否可以找到更小的满足条件的窗口。
-
返回最小窗口长度:最终,窗口大小就是最少替换次数的结果。
详细步骤与例子
假设字符串是 "ASDFASDFAS", 即目标是平衡字符频次到 3(因为总长度 12,所以每个字符的目标频次为 12 / 4 = 3)
-
统计字符频次:
A→ 3S→ 3D→ 3F→ 3
这里每个字符的频次已经是目标频次,所以无需进行任何替换。最小替换次数是
0。 -
再举个例子,比如字符串是
"AADDSFFAA":A→ 4S→ 1D→ 1F→ 2- 每个字符的目标频次是
2(因为长度是8)。
这时我们需要将
A从4降到2,并且S、D、F都需要增加到2。 -
通过滑动窗口调整:
- 从左指针和右指针同时开始,逐步扩展右指针,直到窗口内字符的超额足以进行替换。
- 窗口缩小时,左指针从左边缩减,尽可能减小窗口的大小,找到最小的窗口大小,即最小替换次数。
滑动窗口实现
滑动窗口的实现关键在于:
- 扩展窗口:右指针向右移动,加入新的字符并更新频次。
- 收缩窗口:当当前窗口内的字符已经足够替换以平衡频次时,尝试通过移动左指针缩小窗口,看看是否能找到更小的替换窗口。
时间复杂度分析
- 统计频次:统计字符的频次是
O(n),其中n是字符串的长度。 - 滑动窗口:滑动窗口中的左指针和右指针各自最多走一遍字符串,所以滑动窗口的操作是
O(n)。
因此,整体时间复杂度是 O(n),其中 n 是字符串的长度。
结论
这道题的关键在于通过滑动窗口优化计算最少替换次数,确保字符频次相等。通过滑动窗口,我们可以在 O(n) 的时间复杂度内解决这个问题,比起暴力枚举每个子串要高效得多。
实现代码如下:
from collections import Counter
def solution(input):
n = len(input)
target_count = n // 4 # 每个字符的目标频次
freq = Counter(input)
# 计算每个字符的超额
excess = {ch: freq.get(ch, 0) - target_count for ch in "ASDF"}
# 只有超额字符才能在替换过程中起作用
excess = {ch: max(0, excess[ch]) for ch in excess}
# 如果没有超额字符,说明本来就已经平衡
if sum(excess.values()) == 0:
return 0
# 滑动窗口的变量
left = 0
min_len = n # 最小窗口长度,初始化为字符串长度
# 维护窗口内字符的超额情况
current_excess = Counter()
for right in range(n):
# 将当前字符添加到窗口
current_excess[input[right]] += 1
# 如果窗口满足条件,即超额字符的总数达到平衡要求
while all(current_excess[ch] >= excess[ch] for ch in "ASDF"):
# 计算当前窗口的大小
min_len = min(min_len, right - left + 1)
# 尝试缩小窗口
current_excess[input[left]] -= 1
left += 1
return min_len
return -2
if __name__ == "__main__":
# You can add more test cases here
print(solution("ADDF") == 1 )
print(solution("ASAFASAFADDD") == 3)
时间复杂度
- 字符串的长度是
n,遍历字符串的每个字符,滑动窗口的操作是线性的,所以时间复杂度为O(n)。
今天成功ac了这道题目,希望明天可以继续加油!