python - leetcode - 424. The longest repeated character after replacement [Classic problem solution-greedy sliding window algorithm]

1. Question: 424. The longest repeated character after replacement

Description:
You are given a string s and an integer k. You can select any character in the string and change it to any other uppercase English character. This operation can be performed up to k times.

After performing the above operation, return the length of the longest substring containing the same letters.

Example 1:

输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。

Example 2:

输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。
可能存在其他的方法来得到同样的结果。

hint:

  • 1 <= s.length <= 105
  • s consists of uppercase English letters only
  • 0 <= k <= s.length

2. Problem-solving ideas

First, two concepts need to be distinguished: substring (subarray) and subsequence. These two nouns often appear in titles, so it is very necessary to distinguish them. The substring sub-string (sub-array sub-array) is continuous, but the subsequence subsequence can be discontinuous.

Another expression : find the longest interval in a string, in which the number of characters that appear less frequently does not exceed k.

The above expression is equivalent to the question. The advantage of abstracting it into this expression is that it allows us to know that this is an interval problem. The method often used to find sub-intervals is double pointers.

"Insect-taking method": The process of moving the double pointers is very similar to the process of crawling by an insect: if the front foot does not move, move the rear foot over; if the rear foot does not move, move the front foot forward.

Explain why it is called "greedy sliding window", because in addition to "sliding", it can also "expand", which is also the core element of the solution to this problem.

  • Sliding: The left and right ends of the window move one position to the right at the same time
  • Expansion: The left end of the window is fixed and the right end is moved one position to the right.

1. How to initialize dynamic windows?

Answer: Similar to the idea of ​​dynamic programming, we first initialize a window with a length of 0 and start traversing from the leftmost side of the string

2. When will it be expanded?

Answer: When the substring meets the requirements, expand one bit to the right (greedy, the substring will continue to expand after it has met the requirements)
Question: When the substring meets the requirements (only contains repeated characters)
Follow-up answer: win_len - max_freq <= k
explanation: the number of substitutions k is enough to replace all characters in the current window with the most frequent characters

3. When to swipe?

Answer: When the substring does not meet the requirements, slide one position to the right as a whole (search),
that is, win_len - max_freq > k.
Note that the window length will not be reduced. Even if a better substring cannot be searched later, it will not affect the previous slide. The optimal solution of

Double pointer templates can solve most double pointer problems:

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

3. Code examples

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

Guess you like

Origin blog.csdn.net/qq_43030934/article/details/131677911