目录
- 424. 替换后的最长重复字符
- 思考分析1
- 优化
- 1004. 最大连续1的个数 III
- 友情提醒
- 方法1,基于当前最大频数
- 方法2,基于历史最大频数
424. 替换后的最长重复字符
https://leetcode-cn.com/problems/longest-repeating-character-replacement/
给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。
注意:
字符串长度 和 k 不会超过 10^4。
示例 1:
输入:
s = “ABAB”, k = 2
输出:
4
解释:
用两个’A’替换为两个’B’,反之亦然。
示例 2:
输入:
s = “AABABBA”, k = 1
输出:
4
解释:
将中间的一个’A’替换为’B’,字符串变为 “AABBBBA”。
子串 “BBBB” 有最长重复字母, 答案为 4。
思考分析1
需要注意的地方:
1、何时扩充窗口?
当子串符合要求,向右扩充一位。(贪心思想,满足了要求还要继续膨胀)
2、子串达成什么条件能说明符合要求?
如果最大频数 + k >= 当前窗口长度,(也就是说经过k此修改,我们可以将不是出现次数最多的元素修改为频数最高元素,从而变为重复串,并且这个串的长度是大于此时窗口长度的)那么我们认为是符合条件的,此时更新窗口长度,取历史窗口长度与当前窗口长度的较大值
3、什么时候滑动窗口
当子串不满足条件的时候,窗口整体向右滑动一位,窗口长度不会减少。
class Solution {
public:int characterReplacement(string s, int k) {int left = 0, right = 0;//当前窗口中元素的最高频数int now_max_freq = 0;int hash_map[26] ={0};//窗口长度最大值int max_window_length = 0;while(right < s.size()){//新加入窗口的元素char c = s[right];//窗口内这个元素对应的频数+1hash_map[c - 'A']++;//找到整个窗口内最大频数for(int i = 0; i < 26; i++)now_max_freq = max(now_max_freq,hash_map[i]);//如果最大频数 + k >= 当前窗口长度,那么我们认为是符合条件的,此时更新窗口长度,取历史窗口长度与当前窗口长度的较大值if(now_max_freq + k >= right - left + 1){max_window_length = max(max_window_length,right - left + 1);}//如果不满足整个条件,我们需要将整个窗口平移else{char d = s[left];hash_map[d - 'A']--;left++;}right++;}return max_window_length;}
};
优化
关于优化,首先得知道一点就是我们之前更新窗口长度的条件:
1、先找这个窗口内的最大频数
2、如果这个频数 + k >= 当前窗口长度,我们选择更新窗口长度
由于在寻找最大频数的时候有个比较过程,并且每次新进来一个字符我们就得重新比较26次。(我们可以优化比较,例如加入备忘录什么的)。但这里我们不需要这样做。
我们只需要找到“历史窗口内元素出现最大频数”,然后观察这个频数 + k 是否 >= 当前窗口长度。
由于这道题目要求求解的是最长重复子串,如果当前窗口最大字符重复个数小于历史窗口的最大字符重复个数,完全可以将此窗口忽略掉,因为它必然不可能是最长重复子串。只有当历史窗口的最大字符重复个数更新时,其最大长度才会进行相应的更新。
现在我们将上面的代码稍作修改,改成如下代码:
class Solution {
public:int characterReplacement(string s, int k) {int left = 0, right = 0;//历史最大频数int history_max_freq = 0;int hash_map[26] ={0};int max_window_length = 0;while(right < s.size()){//新加入窗口的元素char c = s[right];hash_map[c - 'A']++;//观察整个元素是不是窗口内出现次数最多的元素,如果是更新history值history_max_freq = max(history_max_freq,hash_map[c - 'A']);if(history_max_freq + k >= right - left + 1){max_window_length = max(max_window_length,right - left + 1);}else{char d = s[left];//窗口左移,被移除的元素在窗口内频数-1hash_map[d - 'A']--;left++;}right++;}return max_window_length;}
};
两种解法性能相差挺大的。
下面一题和本题几乎一模一样,甚至是简化,我们也同样用两种思路来做吧。
1004. 最大连续1的个数 III
https://leetcode-cn.com/problems/max-consecutive-ones-iii/
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。
返回仅包含 1 的最长(连续)子数组的长度。
示例 1:
输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例2:
输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
友情提醒
由于这一题的简化性,最好好好看看两者代码有何区别,不然以为是一样的。(其实性能应该差不多,因为这里在找当前最大频数的时候不需要比较26次了,只需要一次。)
方法1,基于当前最大频数
class Solution {
public:int longestOnes(vector<int>& A, int K) {int left = 0, right = 0;int one_times = 0;int now_max_freq = 0;int max_window_length = 0;while(right < A.size()){int c = A[right];if(c == 1) one_times++;if(one_times + K >= right - left + 1){max_window_length = max(max_window_length,right - left + 1);}else{int d = A[left];if(d == 1) one_times--;left++;}right++;}return max_window_length;}
};
方法2,基于历史最大频数
class Solution {
public:int longestOnes(vector<int>& A, int K) {int left = 0, right = 0;int one_times = 0;int history_max_freq = 0;int max_window_length = 0;while(right < A.size()){int c = A[right];if(c == 1) one_times++;history_max_freq = max(history_max_freq,one_times);if(history_max_freq + K >= right - left + 1){max_window_length = max(max_window_length,right - left + 1);}else{int d = A[left];if(d == 1) one_times--;left++;}right++;}return max_window_length;}
};