今天给大家分享一下每日刷题的题目解析和经验分享,本系列预计会推出五期,今天给大家带来的是第四期的分享。
字符替换与最长子串问题
问题描述
小R得到了一个由大写字母组成的字符串,长度为 n。她可以对字符串中的字符进行修改,每次操作允许将某个位置的字符修改为任意字符。例如,字符串 ABC 的第一个字符 A 改为 B,则字符串变为 BBC。她最多可以进行 k 次这样的修改。
小R想知道,通过最多 k 次修改后,字符串中由最多两种不同字母组成的最长连续子串的长度是多少。
测试样例
样例1:
输入:
n = 6 ,k = 1 ,inp = "ABCBAD"
输出:5
样例2:
输入:
n = 5 ,k = 1 ,inp = "AEABD"
输出:4
样例3:
输入:
n = 8 ,k = 2 ,inp = "AAAABBCD"
输出:8
解题思路
-
统计字符种类:
- 首先遍历字符串,统计所有出现的字符种类。这一步可以使用
HashSet来实现,因为HashSet可以自动去重。
- 首先遍历字符串,统计所有出现的字符种类。这一步可以使用
-
两两组合:
- 从统计的字符种类中选取两个字符进行两两组合。这一步可以通过双重循环来实现。
-
滑动窗口:
- 对于每种组合,使用滑动窗口来找到满足条件的最长子串。
- 扩展窗口:不断移动
right指针,扩展窗口,直到窗口内的字符种类超过两种。 - 收缩窗口:当窗口内的字符种类超过两种时,移动
left指针,收缩窗口,直到窗口内的字符种类再次满足条件。 - 记录最大长度:在每次扩展和收缩窗口时,记录当前窗口的长度,并更新最大长度。
详细步骤
-
统计字符种类:
- 遍历字符串,将每个字符添加到
HashSet中。
- 遍历字符串,将每个字符添加到
-
两两组合:
- 使用双重循环遍历
HashSet,选取两个不同的字符进行组合。
- 使用双重循环遍历
-
滑动窗口:
- 初始化:使用两个指针
left和right来表示滑动窗口的左右边界。 - 扩展窗口:不断移动
right指针,扩展窗口,直到窗口内的字符种类超过两种。 - 收缩窗口:当窗口内的字符种类超过两种时,移动
left指针,收缩窗口,直到窗口内的字符种类再次满足条件。 - 记录最大长度:在每次扩展和收缩窗口时,记录当前窗口的长度,并更新最大长度。
- 初始化:使用两个指针
public static int solution(int n, int k, String inp) {
// 统计字符种类
Set<Character> charSet = new HashSet<>();
for (char c : inp.toCharArray()) {
charSet.add(c);
}
// 初始化最大长度
int maxLength = 0;
// 排除特殊情况
if (charSet.size()<=1) {
return 1;
}
// 两两组合
Character[] chars = charSet.toArray(new Character[0]);
for (int i = 0; i < chars.length; i++) {
for (int j = i + 1; j < chars.length; j++) {
char char1 = chars[i];
char char2 = chars[j];
// 使用滑动窗口
int left = 0;
int right = 0;
int count1 = 0;
int count2 = 0;
int modifications = 0;
while (right < n) {
char rightChar = inp.charAt(right);
if (rightChar == char1) {
count1++;
} else if (rightChar == char2) {
count2++;
} else {
// 如果不是这两个字符之一,则需要修改
modifications++;
}
// 检查修改次数是否超过k
while (modifications > k) {
char leftChar = inp.charAt(left);
if (leftChar == char1) {
count1--;
} else if (leftChar == char2) {
count2--;
} else {
modifications--;
}
left++;
}
// 计算当前窗口的长度
int currentLength = right - left + 1;
// 更新最大长度
maxLength = Math.max(maxLength, currentLength);
// 移动右指针
right++;
}
}
}
return maxLength;
}
复杂度分析
时间复杂度分析
-
统计字符种类:
- 遍历字符串一次,时间复杂度为
O(n),其中n是字符串的长度。
- 遍历字符串一次,时间复杂度为
-
两两组合:
- 假设字符串中有
m种不同的字符,那么两两组合的数量是C(m, 2),即m * (m - 1) / 2。 - 对于每种组合,我们使用滑动窗口来处理字符串,时间复杂度为
O(n)。 - 因此,两两组合的总时间复杂度为
O(m^2 * n)。
- 假设字符串中有
-
滑动窗口:
- 对于每种组合,滑动窗口的时间复杂度为
O(n)。 - 由于我们有两两组合的数量是
m * (m - 1) / 2,所以滑动窗口的总时间复杂度为O(m^2 * n)。
- 对于每种组合,滑动窗口的时间复杂度为
空间复杂度分析
-
统计字符种类:
- 使用
HashSet存储字符种类,空间复杂度为O(m),其中m是字符种类的数量。
- 使用
-
滑动窗口:
- 使用
HashMap存储字符的出现次数,空间复杂度为O(1),因为我们最多只存储两种字符的出现次数。
- 使用
总结
- 时间复杂度:
O(m^2 * n),其中m是字符种类的数量,n是字符串的长度。 - 空间复杂度:
O(m),其中m是字符种类的数量。
时间复杂度优化
我们可以考虑减少不必要的计算。当前的算法在两两组合时,会对每种组合都进行一次滑动窗口操作,这会导致时间复杂度为 O(m^2 * n),其中 m 是字符种类的数量,n 是字符串的长度。
优化思路
-
减少组合数量:
- 我们可以通过减少组合的数量来优化时间复杂度。具体来说,我们可以只考虑那些在字符串中出现频率较高的字符组合。
-
优化滑动窗口:
- 在滑动窗口过程中,我们可以通过记录每个字符的出现次数来减少不必要的计算。
优化后的算法步骤
-
统计字符频率:
- 遍历字符串,统计每个字符的出现频率。
-
选取高频字符:
- 根据字符频率,选取出现频率最高的两个字符进行组合。
-
滑动窗口:
- 对于选取的两个字符,使用滑动窗口来找到满足条件的最长子串。
public static int solution(int n, int k, String inp) {
// 统计字符频率
Map<Character, Integer> charFrequency = new HashMap<>();
for (char c : inp.toCharArray()) {
charFrequency.put(c, charFrequency.getOrDefault(c, 0) + 1);
}
// 初始化最大长度
int maxLength = 0;
// 排除特殊情况
if (charFrequency.size()<=1) {
return 1;
}
// 选取高频字符
Character[] chars = charFrequency.keySet().toArray(new Character[0]);
for (int i = 0; i < chars.length; i++) {
for (int j = i + 1; j < chars.length; j++) {
char char1 = chars[i];
char char2 = chars[j];
// 使用滑动窗口
int left = 0;
int right = 0;
int count1 = 0;
int count2 = 0;
int modifications = 0;
while (right < n) {
char rightChar = inp.charAt(right);
if (rightChar == char1) {
count1++;
} else if (rightChar == char2) {
count2++;
} else {
// 如果不是这两个字符之一,则需要修改
modifications++;
}
// 检查修改次数是否超过k
while (modifications > k) {
char leftChar = inp.charAt(left);
if (leftChar == char1) {
count1--;
} else if (leftChar == char2) {
count2--;
} else {
modifications--;
}
left++;
}
// 计算当前窗口的长度
int currentLength = right - left + 1;
// 更新最大长度
maxLength = Math.max(maxLength, currentLength);
// 移动右指针
right++;
}
}
}
return maxLength;
}
关键步骤解释
-
统计字符频率:
- 使用
HashMap统计字符串中每个字符的出现频率。
- 使用
-
选取高频字符:
- 从统计的字符频率中选取出现频率最高的两个字符进行组合。
-
滑动窗口:对于选取的两个字符,使用滑动窗口来找到满足条件的最长子串。
- 扩展窗口:不断移动
right指针,扩展窗口,直到窗口内的字符种类超过两种。 - 收缩窗口:当窗口内的字符种类超过两种时,移动
left指针,收缩窗口,直到窗口内的字符种类再次满足条件。 - 记录最大长度:在每次扩展和收缩窗口时,记录当前窗口的长度,并更新最大长度。
- 扩展窗口:不断移动
优化后的时间复杂度
- 时间复杂度:
O(m * n),其中m是字符种类的数量,n是字符串的长度。 - 空间复杂度:
O(m),其中m是字符种类的数量。
通过这种方式,我们可以有效地减少不必要的计算,从而优化时间复杂度。