一. 题目:424. 替换后的最长重复字符
描述:
给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。
在执行上述操作后,返回包含相同字母的最长子字符串的长度。
示例 1:
输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。
示例 2:
输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。
可能存在其他的方法来得到同样的结果。
提示:
- 1 <= s.length <= 105
- s 仅由大写英文字母组成
- 0 <= k <= s.length
二. 解题思路
首先需要区分两个概念: 子串(子数组) 和 子序列。这两个名词经常在题目中出现,非常有必要加以区分。子串sub-string(子数组 sub-array)是连续的,而子序列 subsequence 可以不连续。
另外一个表达方式:求字符串中一个最长的区间,该区间内的出现次数较少的字符的个数不超过 k。
上面的表达方式跟题目是等价的,抽象成这种表达方式的好处是让我们知道这是一个区间题,求子区间经常使用的方法就是双指针。
「虫取法」:双指针移动的过程和虫子爬动的过程非常像:前脚不动,把后脚移动过来;后脚不动,把前脚向前移动。
解释一下为什么叫它 “贪心的滑窗”,因为除了会 “滑动”, 它还会 “扩充”,这也是紧扣本题解法的核心要素。
- 滑动:窗口的左端和右端同时向右移动一位
- 扩充:窗口的左端固定,右端向右移动一位
1. 动态窗口如何初始化?
答:类似动态规划的思路,我们先初始化一个长度为 0 的窗口,从字符串的最左侧开始遍历
2. 什么时候扩充?
答:当子串符合要求时,向右扩充一位(贪心,子串已经满足要求了还要继续膨胀)
追问:子串符合要求(只包含重复字符)的情形
追答: win_len - max_freq <= k
解释:可替换次数 k 足以将当前窗内所有字符都替换成最高频字符
3. 什么时候滑动?
答:当子串不符合要求时,整体向右滑动一位(搜索)
即 win_len - max_freq > k
注意窗口长度是不会减少的,即便后面搜索不到更优子串,也不会影响滑动之前的最优解文章来源:https://www.toymoban.com/news/detail-567883.html
双指针的模板,能解决大多数的双指针问题:文章来源地址https://www.toymoban.com/news/detail-567883.html
def findSubstring(s):
N = len(s) # 数组/字符串长度
left, right = 0, 0 # 双指针,表示当前遍历的区间[left, right],闭区间
counter = collections.Counter() # 用于统计 子数组/子区间 是否有效
res = 0 # 保存最大的满足题目要求的 子数组/子串 长度
while right < N: # 当右边的指针没有搜索到 数组/字符串 的结尾
counter[s[right]] += 1 # 增加当前右边指针的数字/字符的计数
while 区间[left, right]不符合题意:# 此时需要一直移动左指针,直至找到一个符合题意的区间
counter[s[left]] -= 1 # 移动左指针前需要从counter中减少left位置字符的计数
left += 1 # 真正的移动左指针,注意不能跟上面一行代码写反
# 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
res = max(res, right - left + 1) # 需要更新结果
right += 1 # 移动右指针,去探索新的区间
return res
三. 代码示例
class Solution(object):
def characterReplacement(self, s, k):
"""
:type s: str
:type k: int
:rtype: int
"""
# 解法一
left = 0
right = 0
dic = collections.defaultdict(int)
maxcount = 0
if len(s) == 0:
return 0
for right in range(len(s)):
dic[s[right]] += 1
maxcount = max(maxcount,dic[s[right]])
if right - left + 1 - maxcount > k:
dic[s[left]] -= 1
left += 1
return right - left + 1
# 解法二
N = len(s)
left, right = 0, 0 # [left, right] 都包含
counter = collections.Counter()
res = 0
while right < N:
counter[s[right]] += 1
while right - left + 1 - counter.most_common(1)[0][1] > k:
counter[s[left]] -= 1
left += 1
res = max(res, right - left + 1)
right += 1
return res
# 解法三
count = [0 for _ in range(26)] # 记录当前窗口的字母出现次数
left = 0 # 滑动窗口左边界
right = 0 # 滑动窗口右边界
retval = 0 # 最长窗口长度
while right < len(s):
count[ord(s[right]) - ord('A')] += 1
benchmark = max(count) # 选择出现次数最多的字母为基准
others = sum(count) - benchmark # 则其他字母需要通过替换操作来变为基准
if others <= k: # 通过与K进行比较来判断窗口是进行扩张?
right += 1
retval = max(retval, right - left) # 记录当前有效窗口长度
else: # 通过与K进行比较来判断窗口还是进行位移?
count[ord(s[left]) - ord('A')] -= 1
left += 1
right += 1 # 这里注意:位移操作需要整个向右移,不仅仅只是left向右
return retval
到了这里,关于python - leetcode - 424. 替换后的最长重复字符【经典题解 - 贪心滑动窗口算法】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!