95.字符替换与最小整数问题
一、问题描述
小 R 获得了一个长度为 n 的由大写字母组成的字符串。她拥有对该字符串中的字符进行修改的操作权限,每次操作能够将字符串中任意位置的字符修改为任意其他字符,且最多可进行 k 次这样的修改操作。
小 R 的目标是通过最多 k 次修改操作后,找出字符串中由最多两种不同字母组成的最长连续子串的长度。例如,给定字符串 “ABCBAD”,若 k = 1,通过合理修改其中某个字符,要确定能得到的由最多两种不同字母组成的最长连续子串的长度。
二、思路解析
本题主要思路是通过遍历所有可能的两种不同大写字母的组合,针对每个组合使用滑动窗口技术来找到满足条件的最长连续子串长度,具体如下:
- 枚举字符对:
- 由于字符串是由大写字母('A' 到 'Z')组成,所以通过两层嵌套循环遍历所有可能的两个不同大写字母的组合(c1 和 c2),以此来考虑所有可能形成最长连续子串的两种字母的情况。
- 滑动窗口技术应用:
- 对于每一对选定的字符 c1 和 c2,使用滑动窗口来在字符串中寻找满足条件的最长连续子串。滑动窗口由左右边界 left 和 right 界定,初始时都指向字符串的开头。
- 在窗口滑动过程中,当窗口右边界 right 所指向的字符是 c1 或 c2 时,直接将右边界向右扩展,因为该字符符合我们所选定的两种字符之一,有可能使连续子串更长。
- 当右边界所指向的字符不是 c1 或 c2 时,需要检查是否还有剩余的修改次数 k。如果有(即 changes < k),则可以将该字符修改为符合条件的字符(c1 或 c2),同时将右边界向右扩展,并增加修改次数 changes。
- 如果没有剩余的修改次数(即 changes >= k),则需要移动左边界 left,直到窗口内再次出现可以通过修改使其符合条件的情况(即窗口内有字符不是 c1 或 c2 且 changes < k),在移动左边界的过程中,每移出一个符合 c1 或 c2 的字符,就减少一次修改次数 changes。
- 更新最大长度:
- 在滑动窗口每次移动(右边界扩展或左边界移动)后,都要更新记录的最大长度 max_len,通过比较当前窗口长度(right - left)和已记录的最大长度,取较大值作为新的最大长度。
三、解题步骤
- 初始化最大长度:
- 定义变量 max_len 并初始化为 0,用于记录通过各种可能的修改情况所得到的由最多两种不同字母组成的最长连续子串的长度。
- 遍历字符对:
- 通过两层嵌套循环遍历所有可能的两个不同大写字母的组合(c1 从 'A' 到 'Z',c2 从 'A' 到 'Z'),对于每一对字符组合进行后续的滑动窗口操作以寻找最长连续子串。在循环过程中,当 c1 和 c2 相同时,跳过该组合,因为我们需要的是两种不同的字母。
- 滑动窗口操作:
- 对于每一对选定的字符 c1 和 c2,初始化滑动窗口的左右边界 left 和 right 都为 0,以及修改次数 changes 为 0。
- 进入 while 循环,只要右边界 right 小于字符串的长度 n,就持续进行以下操作:
- 当 inp [right] == c1 || inp [right] == c2 时,说明右边界所指向的字符符合我们选定的两种字符之一,直接将右边界 right 向右扩展,继续下一次循环判断。
- 当 inp [right]!= c1 && inp [right]!= c2 时,说明右边界所指向的字符不符合选定的两种字符,此时检查修改次数 changes 是否小于 k。如果是,将右边界 right 向右扩展,并增加修改次数 changes;如果不是,就需要移动左边界 left,直到窗口内再次出现可以通过修改使其符合条件的情况,在移动左边界过程中,每移出一个符合 c1 或 c2 的字符,就减少一次修改次数 changes。
- 更新最大长度:
- 在每次滑动窗口的右边界 right 扩展或左边界 left 移动后,计算当前窗口的长度(right - left),并通过比较该长度和已记录的最大长度 max_len,将较大值赋给 max_len,以此更新最大长度记录。
- 返回结果:
- 当完成对所有可能的字符对的滑动窗口操作后,变量 max_len 中存储的就是通过最多 k 次修改后,字符串中由最多两种不同字母组成的最长连续子串的长度,将其作为函数的返回值返回。
四、代码分析
#include <iostream>
#include <string>
int solution(int n, int k, std::string inp) {
int max_len = 0;
// 遍历所有可能的字符对 ('A'到 'Z')
for (char c1 = 'A'; c1 <= 'Z'; ++c1) {
for (char c1 = 'A'; c1 <= 'Z'; ++c1) {
if (c1 == c2) continue; // 跳过相同的字符对
int left = 0, right = 0;
int changes = 0;
// 使用滑动窗口技术
while (right < n) {
// 如果当前字符是c1或c2,直接扩展窗口
if (inp[right] == c1 || inp[right] == c2) {
// 扩展窗口
right++;
} else {
// 如果当前字符不是c1或c2,检查是否可以修改
if (changes < k) {
// 扩展窗口并增加修改次数
right++;
changes++;
} else {
// 如果不能修改,移动左边界直到可以再次修改
while (inp[left] == c1 || inp[left] = c2) {
left++;
}
left++;
changes--;
}
}
// 更新最大长度
max_len = std::max(max_len, right - left);
}
}
}
return max_len;
}
int main() {
// Add your test cases here
std::cout << (solution(6, 1, "ABCBAD") == 5) << std::endl;
std::cout << (solution(5, 1, "AEABD") == 4) << std::endl;
return 0;
}
- 函数定义与参数接收:
solution函数接受三个参数:整数 n 表示输入字符串的长度,整数 k 表示可以进行字符修改的最大次数,字符串 inp 是输入的由大写字母组成的字符串。函数的返回值是通过最多 k 次修改后,字符串中由最多两种不同字母组成的最长连续子串的长度。
- 遍历字符对代码分析:
- 两层嵌套的 for 循环
for (char c1 = 'A'; c1 <= 'Z'; ++c1)和for (char c2 = 'A'; c2 <= 'Z'; ++c2)用于遍历所有可能的两个不同大写字母的组合。当c1 == c2时,通过continue语句跳过该组合,因为我们要找的是两种不同的字母。
- 两层嵌套的 for 循环
- 滑动窗口相关代码分析:
- 对于每一对选定的字符 c1 和 c2,首先初始化滑动窗口的左右边界
left = 0和right = 0,以及修改次数changes = 0。 - 在
while (right < n)循环中:- 当
inp[right] == c1 || inp[right] == c2时,执行right++;将右边界向右扩展,因为当前字符符合选定的两种字符之一。 - 当
inp[right]!= c1 && inp[right]!= c2时,若changes < k,则执行right++;和changes++;,即扩展右边界并增加修改次数;若changes >= k,则通过while (inp[left] == c1 || inp[left] == c2)循环移动左边界,每移出一个符合 c1 或 c2 的字符,执行left++;并changes--,直到可以再次修改。
- 当
- 对于每一对选定的字符 c1 和 c2,首先初始化滑动窗口的左右边界
- 更新最大长度代码分析:
- 在每次滑动窗口操作后,通过
max_len = std::max(max_len, right - left);计算当前窗口长度(right - left)与已记录的最大长度 max_len 的较大值,并将其赋给 max_len,从而不断更新最大长度记录。
- 在每次滑动窗口操作后,通过
- 主函数部分分析:
- 在
main函数中,通过std::cout << (solution(6, 1, "ABCBAD") == 5) << std::endl;和std::cout << (solution(5, 1, "AEABD") == 4) << std::endl;等语句对solution函数进行了简单的测试,输出测试结果是否符合预期。
- 在
五、知识总结
- 枚举思想:
- 本题通过两层嵌套循环遍历所有可能的两个不同大写字母的组合,体现了枚举思想。枚举就是将所有可能的情况一一列举出来进行分析和处理,在本题中通过枚举所有可能的字符对,确保不会遗漏任何一种可能形成最长连续子串的两种字母的组合情况。
- 滑动窗口技术:
- 滑动窗口是一种常用的算法技巧,用于在一个序列(如本题中的字符串)中高效地查找满足一定条件的子序列(如本题中的最长连续子串)。通过维护一个窗口的左右边界,并根据窗口内元素的情况动态地移动边界,能够在一次遍历中完成对各种可能情况的搜索,避免了重复的全序列遍历,提高了算法的效率。
- 字符串处理:
- 在代码中涉及到对字符串的处理,如通过索引访问字符串中的单个字符(如
inp[right]),根据字符的值进行条件判断(如判断是否是特定的字符 c1 或 c2)等。掌握字符串的基本操作,包括索引、比较、修改等,是解决此类涉及字符串问题的基础。
- 在代码中涉及到对字符串的处理,如通过索引访问字符串中的单个字符(如
- 条件判断与循环控制:
- 代码中大量运用了条件判断(如
if语句判断字符是否符合条件、是否还有剩余修改次数等)和循环控制(如for循环遍历字符对、while循环实现滑动窗口的移动等)。熟练掌握这些语句的使用,能够根据具体问题的需求准确地构建算法逻辑,实现对数据的有效处理和操作。
- 代码中大量运用了条件判断(如