【华为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
  • 通过状态相同来判断区间内字符出现偶数次
  • 合理处理环形字符串的边界情况
相关推荐
Haohao+++2 小时前
Stable Diffusion原理解析
人工智能·深度学习·算法
ideaout技术团队5 小时前
leetcode学习笔记2:多数元素(摩尔投票算法)
学习·算法·leetcode
代码充电宝5 小时前
LeetCode 算法题【简单】283. 移动零
java·算法·leetcode·职场和发展
不枯石7 小时前
Matlab通过GUI实现点云的均值滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab·均值算法
不枯石8 小时前
Matlab通过GUI实现点云的双边(Bilateral)滤波(附最简版)
开发语言·图像处理·算法·计算机视觉·matlab
白水先森9 小时前
C语言作用域与数组详解
java·数据结构·算法
想唱rap10 小时前
直接选择排序、堆排序、冒泡排序
c语言·数据结构·笔记·算法·新浪微博
老葱头蒸鸡11 小时前
(27)APS.NET Core8.0 堆栈原理通俗理解
算法
视睿11 小时前
【C++练习】06.输出100以内的所有素数
开发语言·c++·算法·机器人·无人机
柠檬071112 小时前
matlab cell 数据转换及记录
算法