【华为OD】环中最长子串2

【华为OD】环中最长子串2

题目描述

给你一个字符串s,字符串 s 首尾相连成一个环形,请你在环中找出"l"、"o"、"x"字符都恰好出现了偶数次最长子字符串的长度。

输入描述

输入是一串小写的字母组成的字符串。

输出描述

输出是一个整数。

示例

输入:

复制代码
alolobo

输出:

复制代码
6

说明:

最长子字符串之一是"alolob",它包含"l","o"各2个,以及0个"x"。

解题思路

这是一个环形字符串中寻找满足特定条件的最长子串问题。关键在于:

  1. 字符串是环形的,需要考虑跨越首尾的情况
  2. 需要找到"l"、"o"、"x"三个字符都出现偶数次的最长子串
  3. 使用状态压缩和前缀和的思想来优化

核心思想:

  • 使用位运算表示三个字符的奇偶性状态
  • 利用前缀异或和的性质:如果两个位置的状态相同,那么它们之间的子串中三个字符都出现了偶数次
  • 考虑环形结构,将字符串复制一份来处理跨越边界的情况

我将提供两种解法:暴力枚举法状态压缩 + 前缀异或优化法

解法一:暴力枚举法

枚举所有可能的子串,统计每个子串中"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] 中三个字符的奇偶性。

环形处理

对于环形字符串,有两种处理方式:

  1. 将字符串复制一份,变成长度为2n的线性字符串
  2. 对每个起始位置分别处理,使用取模运算处理环形索引

示例分析

对于输入 "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"。

总结

三种解法各有特点:

  1. 暴力枚举法:思路直观,容易理解,但时间复杂度较高
  2. 状态压缩 + 前缀异或优化法:最优解法,利用位运算和异或的性质,时间复杂度O(N)
  3. 优化的环形处理方案:在状态压缩基础上优化环形处理逻辑

对于这道题目,推荐使用状态压缩 + 前缀异或优化法,它不仅时间效率最高,而且充分利用了问题的数学性质。

关键技巧:

  • 使用位运算表示字符的奇偶性状态
  • 利用异或运算的性质:a ^ a = 0
  • 通过状态相同来判断区间内字符出现偶数次
  • 合理处理环形字符串的边界情况
相关推荐
JCBP_3 小时前
QT(3)
开发语言·汇编·c++·qt·算法
研梦非凡3 小时前
ICCV 2025|基于曲线感知高斯溅射的3D参数曲线重建
人工智能·算法·3d
XFF不秃头3 小时前
力扣刷题笔记-三数之和
c++·笔记·算法·leetcode
一碗白开水一4 小时前
【第19话:定位建图】SLAM点云配准之3D-3D ICP(Iterative Closest Point)方法详解
人工智能·算法
编码浪子4 小时前
趣味学RUST基础篇(函数式编程闭包)
开发语言·算法·rust
Want5954 小时前
C/C++圣诞树②
c语言·c++·算法
索迪迈科技5 小时前
算法题(203):矩阵最小路径和
线性代数·算法·矩阵
默默无名的大学生5 小时前
数据结构——链表的基本操作
数据结构·算法
Neverfadeaway5 小时前
C语言————冒泡排序(例题2)
c语言·数据结构·算法·冒泡排序·升序排列·降序排列