【LeetCode】替换后的最长重复字符(双指针)

【LeetCode 0202】424.替换后的最长重复字符(双指针)

题目描述

给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度

注意:字符串长度 和 k 不会超过 104。

示例 1:
输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。
示例 2:
输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-repeating-character-replacement

参考代码(Java)-简洁版

public class Solution {
    
    

    public int characterReplacement(String s, int k) {
    
    
       
        int len = s.length();
        if (len < 2) {
    
    
            return len;
        }

        char[] charArray = s.toCharArray();
        int left = 0;
        int right = 0;
        int[] freq = new int[26];//freq数组统计 当前子串 中每个字符出现次数
        int maxCount = 0;//当前freq中的最大值
		int res = 0;
        
        while (right < len){
    
    
            freq[charArray[right] - 'A']++;
            maxCount = Math.max(maxCount, freq[charArray[right] - 'A']);//更新maxCount
            right++;

            if (right - left > maxCount + k){
    
    
              	// 说明此时 k 不够用
                // 把其它不是最多出现的字符替换以后,都不能填满这个滑动的窗口,这个时候须要考虑左边界向右移动
                // 移出滑动窗口的时候,频数数组须要相应地做减法
                freq[charArray[left] - 'A']--;
                left++;
            }
            res = Math.max(res, right - left);
        }
        return res;
    }
}

参考代码(Java)-注释版

public class Solution {
    
    

    public int characterReplacement(String s, int k) {
    
    
        
        //========================================第一部分:特殊情况讨论√=========================================
        int len = s.length();
        if (len < 2) {
    
    //字符串长度为0或1时候,直接返回
            return len;
        }

		//========================================第二部分:必要变量定义√=========================================
        char[] charArray = s.toCharArray();//字符串s -> 字符数组
        
        int left = 0;
        int right = 0;
		// 技巧(左闭右开):[left, right) 代表的子串长度即 right-left
     
        int[] freq = new int[26];//freq数组统计当前子串中每个字符出现次数
        int maxCount = 0;//当前子串,freq数组里的最大值
        
   		int res = 0;//result记录最长字串长度
        
		//========================================第三部分:指针滑动过程√=========================================
        while (right < len){
    
    //右边界向右移动
            freq[charArray[right] - 'A']++;
            maxCount = Math.max(maxCount, freq[charArray[right] - 'A']);//每次循环 右边界移动 -> 子串变化 -> maxCount更新
            right++;

            if (right - left > maxCount + k){
    
    
              	// 说明此时 k 不够用
                // 把其它不是最多出现的字符替换以后,都不能填满这个滑动的窗口
                //这个时候须要考虑左边界向右移动
                // 移出滑动窗口的时候,频数数组须要相应地做减法
                freq[charArray[left] - 'A']--;
                left++;
            }//end of if
            res = Math.max(res, right - left);//res记录整个过程中动态变化着的 right-left 最大值
        }//end of while
        return res;
    }
}

*代码技巧

  • 技巧一:Java方法 字符串s -> 字符数组

char[] charArray = s.toCharArray();

  • 技巧二字符串长度技巧左闭右开

[left, right) 代表的子串长度right-left

  • 技巧三字符数组统计频数
  • freq[ charArray[right] - ‘A’ ] ++ ;

算法理解(双指针)

这里建议优先顺着链接去看视频,很清楚!

  1. 首先,移动右边界,找到一个满足题意的最长字串(k次替换后),即 纳入下一字符不能满足时停下
  2. 然后,左边界只须要向右移动一格以后,右边界就又可以开始向右移动了,继续尝试找到更长的目标子串;
  3. 替换后的最长重复子串就产生在右边界、左边界交替向右移动的过程中。

复杂度分析

时间复杂度:O(N),这里 N 是输入字符串 S 的长度;
空间复杂度:O(A),这里 A 是输入字符串 S 出现的字符 ASCII 值的范围

细节思考

1.证明:如果长度为 L 的子串不符合题目的要求,那么左边界固定,长度更长的子串也不符合题目的要求。

答:
两个前提:
假设长度为 L 的子串,出现最多的字符为 A,出现x次;其余字符均为 B,出现y次。
由字符 A 出现次数最多,可知 x ≥ y
又由于长度为 L 的子串不符合题目的要求,可知 y > k

起点固定的情况下,考虑更长的子串:

  • 如果接下来看到的字符都是 A,依然须要考虑把之前看到的 B 全部替换成为 A,由于y>k,这是不能做到的;
  • 如果接下来看到的字符不是 A
    若出现最多次字符不变依然为A,依然须要考虑把之前看到的 B 还有新字符全部替换成为 A,由于y>k,所以y+1>k这是不能做到的;
    若出现最多次字符变更,那么须要考虑把之前看到的 A 全部替换成为新的频数最多的字符,由于x≥y>k,这也是不能做到的。

2. maxCount 在内层循环「左边界向右移动一个位置」的过程中,没有维护它的定义,结论是否正确?

答:
结论依然正确

感性理解: 所求最长长度即为maxCount + k,如同记录一样不断被刷新。A创下的记录,下一个B可能达不到,可能打破,但我们只关注记录,如果B打破记录,那么一定有maxCount(B) > maxCount(A),无需维护。

理性推理:「左边界向右移动一个位置」的时候,maxCount 或者不变,或者减 1

  • 不变:例如 s = [AAABBB]、k = 2,左边界 A 移除以后,窗口内字符出现次数不变,依然为 3;
  • 变小:由于 我们要找的只是最长替换 k 次以后重复子串的长度。接下来我们继续让右边界向右移动一格,有两种情况:
    ① 右边界如果读到了刚才移出左边界的字符,恰好 maxCount 的值被正确维护;
    ② 右边界如果读到了不是刚才移出左边界的字符,新的子串要想在符合题意的条件下变得更长,maxCount 一定要比之前的值还要更多,因此不会错过更优的解。

3. 内层循环里的 if 能不能改成 while?

答:可以但没有必要。理由依然是:我们只关心最长替换 k 次以后重复子串的长度。

正是因为多读了一个字符,使得 right - left > maxCount + k 成立;
在 left++ 以后,由于可以不维护 maxCount 的定义,right - left > maxCount + k 不成立。因此 if 里面的代码块只会被执行一次

4. 可以不用一直用 res 记录滑动窗口的最大长度,最后返回 right - left 即可。

答:依然是 我们只关心最长替换 k 次以后重复子串的长度,并且 maxCount 只会增加不会减少。在退出内层 if 语句的时候,区间 [left, right) 不一定是符合要求的子串,但是子串的长度一定等于题目要求的替换 k 次以后字符全都相等的最长子串(maxCount 的值不会变小,所以它会一直撑着滑动窗口的长度直到 right 遍历到字符串的末尾)。这一点如果很难理解的话,我们建议大家使用小测试数据、跟踪代码进行理解。

作者:LeetCode
链接:https://leetcode-cn.com/problems/longest-repeating-character-replacement/solution/ti-huan-hou-de-zui-chang-zhong-fu-zi-fu-eaacp/
来源:力扣(LeetCode)

猜你喜欢

转载自blog.csdn.net/qq_51366188/article/details/113572586