题干: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 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 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 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 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的情况和空串的情况