【华为OD】环中最长子串2
【华为OD】环中最长子串2
题目描述
给你一个字符串s,字符串 s 首尾相连成一个环形,请你在环中找出"l"、“o”、"x"字符都恰好出现了偶数次最长子字符串的长度。
输入描述
输入是一串小写的字母组成的字符串。
输出描述
输出是一个整数。
示例
输入:
alolobo
输出:
6
说明:
最长子字符串之一是"alolob",它包含"l",“o"各2个,以及0个"x”。
解题思路
这是一个环形字符串中寻找满足特定条件的最长子串问题。关键在于:
- 字符串是环形的,需要考虑跨越首尾的情况
- 需要找到"l"、“o”、"x"三个字符都出现偶数次的最长子串
- 使用状态压缩和前缀和的思想来优化
核心思想:
- 使用位运算表示三个字符的奇偶性状态
- 利用前缀异或和的性质:如果两个位置的状态相同,那么它们之间的子串中三个字符都出现了偶数次
- 考虑环形结构,将字符串复制一份来处理跨越边界的情况
我将提供两种解法:暴力枚举法和状态压缩 + 前缀异或优化法。
解法一:暴力枚举法
枚举所有可能的子串,统计每个子串中"l"、“o”、"x"的出现次数,判断是否都为偶数。
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实现
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) % nc = s[pos]# 更新字符计数if c == 'l':count_l += 1elif c == 'o':count_o += 1elif 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++实现
#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实现
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实现
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] = iprint(max_len)solve_optimized()
C++实现
#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实现
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实现
def solve_circular():s = input().strip()n = len(s)max_len = 0# 方法1:直接在原字符串上处理环形for start in range(n):state_pos = {0: start - 1}state = 0for i in range(n):pos = (start + i) % nc = s[pos]if c == 'l':state ^= 1elif c == 'o':state ^= 2elif c == 'x':state ^= 4if state in state_pos:length = start + i - state_pos[state]max_len = max(max_len, length)else:state_pos[state] = start + iprint(max_len)solve_circular()
C++实现
#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
- 通过状态相同来判断区间内字符出现偶数次
- 合理处理环形字符串的边界情况