leetcode 滑動視窗小結 (二)
技術標籤:# 演算法:滑動視窗leetcode刷題筆記字串滑動視窗
目錄
424. 替換後的最長重複字元
https://leetcode-cn.com/problems/longest-repeating-character-replacement/
給你一個僅由大寫英文字母組成的字串,你可以將任意位置上的字元替換成另外的字元,總共可最多替換 k 次。在執行上述操作後,找到包含重複字母的最長子串的長度。
注意:
字串長度 和 k 不會超過 10^4。
輸入:
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];
//視窗內這個元素對應的頻數+1
hash_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];
//視窗左移,被移除的元素在視窗內頻數-1
hash_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;
}
};