【华为OD】环中最长子串2
题目描述
给你一个字符串s,字符串 s 首尾相连成一个环形,请你在环中找出"l"、"o"、"x"字符都恰好出现了偶数次最长子字符串的长度。
输入描述
输入是一串小写的字母组成的字符串。
输出描述
输出是一个整数。
示例
输入:
alolobo
输出:
6
说明:
最长子字符串之一是"alolob",它包含"l","o"各2个,以及0个"x"。
解题思路
这是一个环形字符串中寻找满足特定条件的最长子串问题。关键在于:
- 字符串是环形的,需要考虑跨越首尾的情况
- 需要找到"l"、"o"、"x"三个字符都出现偶数次的最长子串
- 使用状态压缩和前缀和的思想来优化
核心思想:
- 使用位运算表示三个字符的奇偶性状态
- 利用前缀异或和的性质:如果两个位置的状态相同,那么它们之间的子串中三个字符都出现了偶数次
- 考虑环形结构,将字符串复制一份来处理跨越边界的情况
我将提供两种解法:暴力枚举法 和状态压缩 + 前缀异或优化法。
解法一:暴力枚举法
枚举所有可能的子串,统计每个子串中"l"、"o"、"x"的出现次数,判断是否都为偶数。
Java实现
java
import java.util.*;
public class Solution1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
sc.close();
int n = s.length();
int maxLen = 0;
// 为了处理环形,将字符串复制一份
String doubled = s + s;
// 枚举所有可能的起始位置
for (int start = 0; start < n; start++) {
int countL = 0, countO = 0, countX = 0;
// 枚举以start为起点的所有子串
for (int len = 1; len <= n; len++) {
int pos = (start + len - 1) % n;
char c = s.charAt(pos);
// 更新字符计数
if (c == 'l') countL++;
else if (c == 'o') countO++;
else if (c == 'x') countX++;
// 检查是否满足条件(三个字符都出现偶数次)
if (countL % 2 == 0 && countO % 2 == 0 && countX % 2 == 0) {
maxLen = Math.max(maxLen, len);
}
}
}
System.out.println(maxLen);
}
}
Python实现
python
def solve_brute_force():
s = input().strip()
n = len(s)
max_len = 0
# 枚举所有可能的起始位置
for start in range(n):
count_l = count_o = count_x = 0
# 枚举以start为起点的所有子串
for length in range(1, n + 1):
pos = (start + length - 1) % n
c = s[pos]
# 更新字符计数
if c == 'l':
count_l += 1
elif c == 'o':
count_o += 1
elif c == 'x':
count_x += 1
# 检查是否满足条件(三个字符都出现偶数次)
if count_l % 2 == 0 and count_o % 2 == 0 and count_x % 2 == 0:
max_len = max(max_len, length)
print(max_len)
solve_brute_force()
C++实现
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.length();
int maxLen = 0;
// 枚举所有可能的起始位置
for (int start = 0; start < n; start++) {
int countL = 0, countO = 0, countX = 0;
// 枚举以start为起点的所有子串
for (int len = 1; len <= n; len++) {
int pos = (start + len - 1) % n;
char c = s[pos];
// 更新字符计数
if (c == 'l') countL++;
else if (c == 'o') countO++;
else if (c == 'x') countX++;
// 检查是否满足条件(三个字符都出现偶数次)
if (countL % 2 == 0 && countO % 2 == 0 && countX % 2 == 0) {
maxLen = max(maxLen, len);
}
}
}
cout << maxLen << endl;
return 0;
}
解法二:状态压缩 + 前缀异或优化法
使用位运算来表示三个字符的奇偶性状态,利用前缀异或和的性质来快速判断子串是否满足条件。
Java实现
java
import java.util.*;
public class Solution2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
sc.close();
int n = s.length();
int maxLen = 0;
// 状态压缩:用3位二进制表示l、o、x的奇偶性
// 第0位表示l,第1位表示o,第2位表示x
// 0表示偶数次,1表示奇数次
// 记录每个状态第一次出现的位置
Map<Integer, Integer> statePos = new HashMap<>();
statePos.put(0, -1); // 初始状态,所有字符都出现0次(偶数次)
int state = 0;
// 处理环形:遍历两倍长度
for (int i = 0; i < 2 * n; i++) {
char c = s.charAt(i % n);
// 更新状态
if (c == 'l') state ^= 1; // 第0位异或
else if (c == 'o') state ^= 2; // 第1位异或
else if (c == 'x') state ^= 4; // 第2位异或
if (statePos.containsKey(state)) {
// 找到相同状态,计算子串长度
int len = i - statePos.get(state);
if (len <= n) { // 确保不超过原字符串长度
maxLen = Math.max(maxLen, len);
}
} else {
statePos.put(state, i);
}
}
System.out.println(maxLen);
}
}
Python实现
python
def solve_optimized():
s = input().strip()
n = len(s)
max_len = 0
# 状态压缩:用3位二进制表示l、o、x的奇偶性
# 第0位表示l,第1位表示o,第2位表示x
# 0表示偶数次,1表示奇数次
# 记录每个状态第一次出现的位置
state_pos = {0: -1} # 初始状态,所有字符都出现0次(偶数次)
state = 0
# 处理环形:遍历两倍长度
for i in range(2 * n):
c = s[i % n]
# 更新状态
if c == 'l':
state ^= 1 # 第0位异或
elif c == 'o':
state ^= 2 # 第1位异或
elif c == 'x':
state ^= 4 # 第2位异或
if state in state_pos:
# 找到相同状态,计算子串长度
length = i - state_pos[state]
if length <= n: # 确保不超过原字符串长度
max_len = max(max_len, length)
else:
state_pos[state] = i
print(max_len)
solve_optimized()
C++实现
cpp
#include <iostream>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.length();
int maxLen = 0;
// 状态压缩:用3位二进制表示l、o、x的奇偶性
// 第0位表示l,第1位表示o,第2位表示x
// 0表示偶数次,1表示奇数次
// 记录每个状态第一次出现的位置
unordered_map<int, int> statePos;
statePos[0] = -1; // 初始状态,所有字符都出现0次(偶数次)
int state = 0;
// 处理环形:遍历两倍长度
for (int i = 0; i < 2 * n; i++) {
char c = s[i % n];
// 更新状态
if (c == 'l') state ^= 1; // 第0位异或
else if (c == 'o') state ^= 2; // 第1位异或
else if (c == 'x') state ^= 4; // 第2位异或
if (statePos.find(state) != statePos.end()) {
// 找到相同状态,计算子串长度
int len = i - statePos[state];
if (len <= n) { // 确保不超过原字符串长度
maxLen = max(maxLen, len);
}
} else {
statePos[state] = i;
}
}
cout << maxLen << endl;
return 0;
}
解法三:优化的环形处理方案
考虑到环形的特殊性,提供一个更清晰的处理方案:
Java实现
java
import java.util.*;
public class Solution3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
sc.close();
int n = s.length();
int maxLen = 0;
// 方法1:直接在原字符串上处理环形
for (int start = 0; start < n; start++) {
Map<Integer, Integer> statePos = new HashMap<>();
statePos.put(0, start - 1);
int state = 0;
for (int i = 0; i < n; i++) {
int pos = (start + i) % n;
char c = s.charAt(pos);
if (c == 'l') state ^= 1;
else if (c == 'o') state ^= 2;
else if (c == 'x') state ^= 4;
if (statePos.containsKey(state)) {
int len = start + i - statePos.get(state);
maxLen = Math.max(maxLen, len);
} else {
statePos.put(state, start + i);
}
}
}
System.out.println(maxLen);
}
}
Python实现
python
def solve_circular():
s = input().strip()
n = len(s)
max_len = 0
# 方法1:直接在原字符串上处理环形
for start in range(n):
state_pos = {0: start - 1}
state = 0
for i in range(n):
pos = (start + i) % n
c = s[pos]
if c == 'l':
state ^= 1
elif c == 'o':
state ^= 2
elif c == 'x':
state ^= 4
if state in state_pos:
length = start + i - state_pos[state]
max_len = max(max_len, length)
else:
state_pos[state] = start + i
print(max_len)
solve_circular()
C++实现
cpp
#include <iostream>
#include <string>
#include <unordered_map>
#include <algorithm>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.length();
int maxLen = 0;
// 方法1:直接在原字符串上处理环形
for (int start = 0; start < n; start++) {
unordered_map<int, int> statePos;
statePos[0] = start - 1;
int state = 0;
for (int i = 0; i < n; i++) {
int pos = (start + i) % n;
char c = s[pos];
if (c == 'l') state ^= 1;
else if (c == 'o') state ^= 2;
else if (c == 'x') state ^= 4;
if (statePos.find(state) != statePos.end()) {
int len = start + i - statePos[state];
maxLen = max(maxLen, len);
} else {
statePos[state] = start + i;
}
}
}
cout << maxLen << endl;
return 0;
}
算法复杂度分析
解法一:暴力枚举法
- 时间复杂度:O(N²),需要枚举所有可能的子串
- 空间复杂度:O(1)
解法二:状态压缩 + 前缀异或优化法
- 时间复杂度:O(N),只需要遍历字符串
- 空间复杂度:O(1),状态数量最多8种(2³)
解法三:优化的环形处理方案
- 时间复杂度:O(N²),但常数较小
- 空间复杂度:O(1)
算法原理详解
状态压缩的核心思想
使用3位二进制数来表示"l"、"o"、"x"三个字符的奇偶性状态:
- 第0位:'l'字符的奇偶性(0=偶数,1=奇数)
- 第1位:'o'字符的奇偶性(0=偶数,1=奇数)
- 第2位:'x'字符的奇偶性(0=偶数,1=奇数)
前缀异或的性质
如果两个位置的状态相同,说明从第一个位置到第二个位置之间的子串中,三个字符都出现了偶数次。
这是因为:state[j] = state[i]
意味着 state[i] ^ state[j] = 0
,而 state[i] ^ state[j]
正好表示区间 [i+1, j]
中三个字符的奇偶性。
环形处理
对于环形字符串,有两种处理方式:
- 将字符串复制一份,变成长度为2n的线性字符串
- 对每个起始位置分别处理,使用取模运算处理环形索引
示例分析
对于输入 "alolobo"
:
使用状态压缩方法分析:
- 初始状态:000(l=0, o=0, x=0)
- 处理'a':状态不变,仍为000
- 处理'l':状态变为001(l=1, o=0, x=0)
- 处理'o':状态变为011(l=1, o=1, x=0)
- 处理'l':状态变为010(l=0, o=1, x=0)
- 处理'o':状态变为000(l=0, o=0, x=0)
- 处理'b':状态不变,仍为000
- 处理'o':状态变为010(l=0, o=1, x=0)
在位置4时,状态回到000,与初始状态相同,说明子串"alolo"中三个字符都出现偶数次,长度为5。
继续分析可以找到长度为6的子串"alolob"。
总结
三种解法各有特点:
- 暴力枚举法:思路直观,容易理解,但时间复杂度较高
- 状态压缩 + 前缀异或优化法:最优解法,利用位运算和异或的性质,时间复杂度O(N)
- 优化的环形处理方案:在状态压缩基础上优化环形处理逻辑
对于这道题目,推荐使用状态压缩 + 前缀异或优化法,它不仅时间效率最高,而且充分利用了问题的数学性质。
关键技巧:
- 使用位运算表示字符的奇偶性状态
- 利用异或运算的性质:
a ^ a = 0
- 通过状态相同来判断区间内字符出现偶数次
- 合理处理环形字符串的边界情况