📜  每个字符出现偶数次的最长子串的长度(1)

📅  最后修改于: 2023-12-03 15:40:39.619000             🧑  作者: Mango

每个字符出现偶数次的最长子串的长度

介绍

给定一个字符串,找到长度最长的子串,使得该子串中每个字符出现的次数都是偶数。

例如,给定字符串 "abccccdd",其最长子串为 "cccc",因为 "cccc" 中每个字符 'c' 和 'd' 都出现了偶数次。

解法
哈希表

一种常见的解法是使用哈希表来统计每个字符出现的次数。对于字符串中的每个字符,我们统计其出现次数,然后判断每个字符的出现次数是否都是偶数。

具体来说,我们可以遍历字符串,然后使用哈希表来记录每个字符出现的次数。遍历完成后,我们再次遍历字符串,对于每次遍历到的字符,我们向左和向右扩展,直到该子串中每种字符的出现次数都是偶数为止。

实现时,我们可以使用一个布尔型数组来记录每个字符出现的次数是否是偶数。初始化时,数组的所有元素都是 false。遍历字符串时,每次遇到一个字符,我们将该字符的计数器加 1,如果该字符的计数器是偶数,我们将该字符出现次数为偶数的标记位置为 true。遍历完字符串后,我们就得到了每个字符出现的次数以及每个字符是否出现偶数次的信息。最后,我们再次遍历字符串,对每个字符进行向左和向右扩展的操作。

该算法的时间复杂度是 $O(n^2)$,其中 n 是字符串的长度。具体来说,遍历字符串需要 $O(n)$ 的时间,对每个字符进行扩展需要 $O(n)$ 的时间,因此总时间复杂度是 $O(n^2)$。空间复杂度是 $O(1)$。

class Solution {
    public int findTheLongestSubstring(String s) {
        int n = s.length();
        boolean[] even = new boolean[1 << 5];
        int ans = 0;
        even[0] = true;
        int mask = 0;
        for (int i = 0; i < n; i++) {
            char ch = s.charAt(i);
            if (ch == 'a') {
                mask ^= 1;
            } else if (ch == 'e') {
                mask ^= 2;
            } else if (ch == 'i') {
                mask ^= 4;
            } else if (ch == 'o') {
                mask ^= 8;
            } else if (ch == 'u') {
                mask ^= 16;
            }
            if (even[mask]) {
                ans = Math.max(ans, i + 1);
            } else {
                even[mask] = true;
                for (int j = 0; j < 5; j++) {
                    if (even[mask ^ (1 << j)]) {
                        ans = Math.max(ans, i - findFirst(even, mask ^ (1 << j)) + 1);
                    }
                }
            }
        }
        return ans;
    }

    private int findFirst(boolean[] even, int mask) {
        for (int i = 0; i < mask; i++) {
            if (even[i] && (i ^ mask) != mask) {
                return i + 1;
            }
        }
        return -1;
    }
}

Python 代码类似:

class Solution:
    def findTheLongestSubstring(self, s: str) -> int:
        n = len(s)
        even = [-1] * (1 << 5)
        ans = 0
        even[0] = 0
        mask = 0
        for i in range(n):
            if s[i] == 'a':
                mask ^= 1
            elif s[i] == 'e':
                mask ^= 2
            elif s[i] == 'i':
                mask ^= 4
            elif s[i] == 'o':
                mask ^= 8
            elif s[i] == 'u':
                mask ^= 16
            if even[mask] != -1:
                ans = max(ans, i + 1 - even[mask])
            else:
                even[mask] = i + 1
        return ans
位运算

我们可以使用一个状态变量来记录每个字符出现的次数是否是偶数,使用二进制数字的每一位表示每个字符的奇偶性,其中第 $i$ 位表示第 $i$ 个字符出现次数是否是奇数。例如,对于字符串 "aabbcc",其状态变量的初始值为 000000,表示每个字符出现次数都是偶数。

当我们遍历到第 $i$ 个字符时,我们检查该字符是否为元音字母。如果是,我们就更新状态变量,将当前元音字母出现次数的奇偶性反转。例如,对于字符串 "aabbcc",当我们处理到第一个元音字母 'a' 时,状态变量会变为 000001。当我们处理到第二个元音字母 'a' 时,状态变量会变为 000000,因为第一个和第二个 'a' 出现次数的奇偶性相同。

我们将状态变量设为 32 位的整数,每次更新状态变量时,我们对其进行异或操作。具体来说,对于元音字母 'a',我们将状态变量的第 0 位取反,对于元音字母 'e',我们将状态变量的第 1 位取反,以此类推。

在记录状态变量时,我们需要记录该状态最早出现的位置。对于当前元音字母出现次数情况,如果该状态曾经出现过,我们就可以利用该状态最早出现的位置来更新答案。

该解法的时间复杂度为 $O(n^3)$,其中 n 是字符串的长度。具体来说,枚举子串需要 $O(n^2)$ 的时间,暴力判断子串中每个字符出现次数是否为偶数需要 $O(n)$ 的时间,因此总时间复杂度是 $O(n^3)$。空间复杂度是 $O(n)$,即哈希表使用的空间。

class Solution:
    def findTheLongestSubstring(self, s: str) -> int:
        n = len(s)
        ans = 0
        index = {0: -1}
        mask = 0
        for i in range(n):
            if s[i] == 'a':
                mask ^= 1
            elif s[i] == 'e':
                mask ^= 2
            elif s[i] == 'i':
                mask ^= 4
            elif s[i] == 'o':
                mask ^= 8
            elif s[i] == 'u':
                mask ^= 16
            if mask in index:
                ans = max(ans, i - index[mask])
            else:
                index[mask] = i
        return ans