LeetCode 1156. 单字符重复子串的最大长度
题目描述


题意分析
可以在字符串中任选两个位置交换一次,也可以不交换。求操作后能够得到的最长连续同字符子串长度,子串中的每个字符都必须相同。
交换只改变位置,不会增加某种字符的总数量。因此即使某段只差一个字符就能连成更长的一段,也要确认整个字符串中有足够多的目标字符。
解法:枚举目标字符的滑动窗口
核心思路
[!blue]
固定最终子串使用的目标字母。一次交换最多从区间外换入一个目标字符,因此最终能变成全目标字符的区间,在交换前至多包含一个非目标字符。用滑动窗口维护这个限制,就能枚举可能的区间,而不必枚举两个交换位置。
先统计目标字母在全串的次数
freq[target]。扫描时用diff记录窗口内非目标字符数,加入右端后若diff > 1,就持续移动左端,直到重新只剩至多一个异类。对一个长度为
len的合法窗口,如果没有异类,它已经是一段同字符子串。如果有一个异类且len <= freq[target],窗口内只有len - 1个目标字符,全局数量又至少为len,说明窗口外一定还有一个,可以交换进来,形成长度为len的连续段。若窗口长度超过全局次数,因为异类至多一个,只可能有
len = freq[target] + 1。这时不能得到整个窗口长度,但一定能得到少一格的长度:异类在端点时直接舍去该端点;异类在内部时,把它与窗口端点的一个目标字符交换,再舍去成为异类的端点。剩余部分连续且全部为目标字符。所以每个合法窗口能够贡献的长度恰好为
min(len, freq[target])。固定右端点时,更长合法窗口的这个值不会更小,因此保留滑动窗口中最长的合法范围即可。对出现过的每种字母分别扫描,再取最大值,就覆盖了所有可能的目标字符与区间。
解题步骤
- 统计每个小写字母在整串中的出现次数。
- 枚举出现过的目标字母,初始化左端点和窗口异类数
diff = 0。- 向右扩展窗口,右端不是目标字母就令
diff++。- 异类超过一个时缩小左端;移出异类时减少
diff,直到窗口重新合法。- 用窗口长度与目标总次数的较小值更新答案,完成所有目标字母的扫描后返回最大长度。
代码实现
class Solution {
public int maxRepOpt1(String text) {
int[] freq = new int[26];
for (int i = 0; i < text.length(); i++) {
freq[text.charAt(i) - 'a']++;
}
int ans = 0;
for (int target = 0; target < 26; target++) {
if (freq[target] == 0) {
continue;
}
int left = 0;
int diff = 0;
for (int right = 0; right < text.length(); right++) {
if (text.charAt(right) - 'a' != target) {
diff++;
}
// 一次交换最多补一个异类位置,超过时收缩。
while (diff > 1) {
if (text.charAt(left) - 'a' != target) {
diff--;
}
left++;
}
int len = right - left + 1;
// 交换不创造字符,实际长度不能超过全局数量。
ans = Math.max(ans, Math.min(len, freq[target]));
}
}
return ans;
}
}
func maxRepOpt1(text string) int {
freq := make([]int, 26)
for i := 0; i < len(text); i++ {
freq[text[i]-'a']++
}
ans := 0
for target := 0; target < 26; target++ {
if freq[target] == 0 {
continue
}
left := 0
diff := 0
for right := 0; right < len(text); right++ {
if int(text[right]-'a') != target {
diff++
}
// 一次交换最多补一个异类位置,超过时收缩。
for diff > 1 {
if int(text[left]-'a') != target {
diff--
}
left++
}
length := right - left + 1
// 交换不创造字符,实际长度不能超过全局数量。
if length > freq[target] {
length = freq[target]
}
if length > ans {
ans = length
}
}
}
return ans
}
复杂度分析
- 时间复杂度:$O(26n)$,每个目标字母的左右指针都只单向移动,固定小写字母表下为 $O(n)$。
- 空间复杂度:$O(1)$,仅保存
26个频次和固定数量的窗口变量。
关键点总结
[!green]
- 窗口的异类数量限制一次交换能修复几个位置,全局频次限制实际有多少目标字符可用。
- 频次封顶后的长度有明确构造方式,即使没有窗口外字符可换入,也能通过端点处理达到。
- 每个最终答案都有一种目标字母,逐字母扫描能够完整覆盖候选。
易错点总结
[!yellow]
- 只维护一个异类却不按全局次数封顶,会虚构字符串中不存在的额外目标字符。
- 允许两个异类留在窗口,一次交换无法把两处都变成目标字符。
- 把操作当作任意替换,会忽略交换前后字符总量不变的约束。
- 缩左时只有移出异类才移动指针,会卡在窗口开头的目标字符上;每次都应移动左端。
- 不允许零次交换,会错误处理原本已经全部相同的输入。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 424. 替换后的最长重复字符 | 中等 | 原题可直接替换字符,本题只能交换已有字符,合并重复段的长度还受全局字符总次数限制。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!