題幹:https://leetcode.com/problems/longest-repeating-character-replacement/
這題我感覺有點hard難度的味道。
先說下思路,要你找修改後最長的連同字符串,這裏面變數很多,首先,改哪些位置,第二這些位置改成啥,再然後改完後的連同子串長度是多少,全都是變數,感覺複雜超高。
但是如果我們嘗試減少一個變數,例如固定住連同子串的長度,會不會簡單點呢?我們來分析這一點,讓你通過k次修改把一個長度爲X的子串改成全同,說明什麼?這個子串中字符出現最多的那個字符至少有X-k個,這樣把剩餘k個全改掉就肯定能做到。
注意題幹是說“至多”k個,所以如果那個字符比X-k還要多,就更可以做到了。
這就引申出了本題的核心邏輯,找到"某段內"子串中各個字符的出現次數統計數據,進而找到最大出現次數max, 用段長-max,得到的差值如果小於等於k次,就說明可以在k次操作內完成。
更高一層邏輯,就是我們怎麼找段,難不成i, j全部遍歷一遍?且每次從頭計算這個統計數據?那這不就成了O(n^3)的時間複雜度了嗎?肯定不行!
於是這裏就有個取巧的方法了,其實這就有點像數組找某種規則下的最大窗口(記爲由窗口左端下標i,和窗口右端下標j),這樣的窗口當然有很多個,但是我們要找的是符合上述規則的最大窗口。
這時候就直覺地使用雙指針法了(如果你做題不夠多,或者沒聽過雙指針法,八成是不會有這種直覺的)
可是直覺可以這麼用,是不是真的可以呢?需要驗證,雙指針法就是用兩個指針分別指向窗口左端和右端,然後先嚐試移動右端,直到條件不合法,再移動左端窗口,直到窗口合法,再不斷移動右窗口,直到不合法,以此類推,直到右窗口達到數組最末。
這裏通常就需要窗口和數組具有如下性質:
1,右端窗口越往右擴張,就越容易不滿足條件,且一旦達到一個不滿足條件的臨界點,再往右就不可能滿足了,這時只能通過移動左端,縮小窗口,才能重新使得窗口合法
2,左窗口越往右擴展(即窗口收縮)越容易滿足條件,且一旦達到滿足條件的臨界點,再往右(只要不超過右端)移動,就都能合法。
我們的這題符合這兩個性質嗎?不難驗證, 符合的,所以可以大膽使用。
另外一點,是如何更新統計窗口內字符出現字數的數據結構?難不成每個窗口都從零算一遍?當然沒必要,因爲每次我們調整窗口只移動1格而已,所以只要基於前一次的統計數據做一次更新即可。
說了這麼多,上代碼:
package com.example.demo.leetcode;
import java.util.HashMap;
import java.util.Map;
public class LongestRepeatingCharacterReplacement {
/**
* 雙指針法測試窗口,找到符合條件的最大窗口
* @param s
* @param k
* @return
*/
public int characterReplacement(String s, int k) {
if(s.length()<1){
return 0;
}
int i=0;
int j=0;
Map<Character, Integer> statsMap = new HashMap<>();
//初始化Map
statsMap.put(s.charAt(0), 1);
int max = Integer.MIN_VALUE;
while(j<=s.length()-1){
while(changeAble(i, j, k, statsMap)){
max = Math.max(j-i+1, max);
j++;
if(j>s.length()-1){
break;
}
updateStatMapRightForward(statsMap, s, j);
}
while(!changeAble(i,j,k, statsMap)){
i++;
if(i>s.length()-1){
break;
}
updateStatMapLeftForward(statsMap, s, i);
}
}
return max;
}
/**
* 本函數爲核心邏輯,檢查從start 到end這段內, 子串的長度減去最大出現字符數之差,小於等於k, 符合則返回true
* @param start
* @param end
* @param k
* @param statsMap
* @return
*/
private boolean changeAble(int start, int end, int k, Map<Character, Integer> statsMap){
int length = end-start+1;
int maxOccur = statsMap.entrySet().stream().map(r->{return r.getValue();}).max(Integer::compare).get();
return length-maxOccur<=k;
}
/**
* 擴張窗口右端
* @param statMap
* @param s
* @param index
*/
private void updateStatMapRightForward(Map<Character, Integer> statMap, String s, int index){
if(statMap.get(s.charAt(index))==null){
statMap.put(s.charAt(index), 1);
}else{
statMap.put(s.charAt(index), statMap.get(s.charAt(index))+1);
}
}
/**
* 收縮窗口左端
* @param statMap
* @param s
* @param index
*/
private void updateStatMapLeftForward(Map<Character, Integer> statMap, String s, int index){
statMap.put(s.charAt(index-1), statMap.get(s.charAt(index-1))-1);
}
public static void main(String[] args) {
LongestRepeatingCharacterReplacement demo = new LongestRepeatingCharacterReplacement();
String s = "AAAA";
int k = 0;
int ret = demo.characterReplacement(s,k);
System.out.println(ret);
}
}
邊界條件注意一下:k爲0的情況和空串的情況