【题型总结】寻找满足字符出现奇数次/偶数次的子串个数或者子串最长长度

简介: 【题型总结】寻找满足字符出现奇数次/偶数次的子串个数或者子串最长长度

寻找满足字符出现奇数次/偶数次的子串个数或者子串最长长度:前缀和+异或+哈希表

题目要求寻找某种特性的子串的数目或者最长长度,而这种特性与字符出现次数的奇偶性相关,那么可以使用前缀和+异或+哈希表解决


构建回文串检测【LC1177】

给你一个字符串 s,请你对 s 的子串进行检测。

每次检测,待检子串都可以表示为 queries[i] = [left, right, k]。我们可以 重新排列 子串 s[left], ..., s[right],并从中选择 最多 k 项替换成任何小写英文字母。

如果在上述检测过程中,子串可以变成回文形式的字符串,那么检测结果为 true,否则结果为 false

返回答案数组 answer[],其中 answer[i] 是第 i 个待检子串 queries[i] 的检测结果。

注意:在替换时,子串中的每个字母都必须作为 独立的 项进行计数,也就是说,如果 s[left..right] = "aaa"k = 2,我们只能替换其中的两个字母。(另外,任何检测都不会修改原始字符串 s,可以认为每次检测都是独立的)


image.png

class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        // 怎么快速求出子串中每种字母的个数?
        // 前缀和:存储每个字母在字符串[0,i]中出现的个数
        int n = s.length();
        int[][] count = new int[n + 1][26];
        for (int i = 0; i < n; i++){
            for (int j = 0; j < 26; j++){
                count[i + 1][j] = count[i][j];
                if (j == s.charAt(i) - 'a'){
                    count[i + 1][j]++;
                }
            }
        }
        List<Boolean> res = new ArrayList<>();
        for (int[] q : queries){
            int l = q[0], r = q[1], k = q[2];
            int num = 0;
            for (int i = 0; i < 26; i++){
                if ((count[r + 1][i] - count[l][i]) % 2 == 1){
                    num++;
                }
            }
            if (num / 2 <= k){
                res.add(true);
            }else{
                res.add(false);
            }
        }
        return res;
    }
}

image.png

位运算
  • 思路

由于只关心每种字母出现次数的奇偶性,所以不需要在前缀和中存储每种字母的出现次数,只需要保存每种字母出现次数的奇偶性。


为方便计算,用 0表示出现偶数次,用 1 表示出现奇数次。


注意只有奇数减偶数,或者偶数减奇数,才能得到奇数。所以如果相减的结果不为 0(或者说相减的两个数不相等),就表示出现了奇数次—》【异或】


作者:灵茶山艾府

链接:https://leetcode.cn/problems/can-make-palindrome-from-substring/solutions/2309725/yi-bu-bu-you-hua-cong-qian-zhui-he-dao-q-yh5p/

来源:力扣(LeetCode)

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

实现

class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        int n = s.length();
        var sum = new int[n + 1];
        for (int i = 0; i < n; i++) {
            int bit = 1 << (s.charAt(i) - 'a');
            sum[i + 1] = sum[i] ^ bit; // 该比特对应字母的奇偶性:奇数变偶数,偶数变奇数
        }
        var ans = new ArrayList<Boolean>(queries.length); // 预分配空间
        for (var q : queries) {
            int left = q[0], right = q[1], k = q[2];
            int m = Integer.bitCount(sum[right + 1] ^ sum[left]);// 奇数字母的个数
            ans.add(m / 2 <= k);
        }
        return ans;
    }
}
作者:灵茶山艾府   
链接:https://leetcode.cn/problems/can-make-palindrome-from-substring/solutions/2309725/yi-bu-bu-you-hua-cong-qian-zhui-he-dao-q-yh5p/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

image.png

  • Tips:异或可以视作「不进位加法(减法)」或者「模 222 剩余系中的加法(减法)」,所以也常常用来解决一些和奇偶性有关的问题。

每个元音包含偶数次的最长子字符串【LC1371】

给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。

前缀和+位运算+枚举
  • 思路:
    使用前缀和和位运算state记录元音字母次数的奇偶性,从大到小枚举子串长度,判断该子串是否所有元音字母都出现偶数次,如果是,那么直接返回该长度
  • 实现
class Solution {
    public int findTheLongestSubstring(String s) {
        int n = s.length();
        int[] sum = new int[n + 1];
        for (int i = 0; i < n; i++){
            if (isVowel(s.charAt(i))){
                int bit = 1 << (s.charAt(i) - 'a');
                sum[i + 1] = sum[i] ^ bit;
            }else{
                sum[i + 1] = sum[i];
            }                  
        }
        for (int len = n ; len > 0; len--){
            for (int i = 0; i + len <= n; i++){
                if ((sum[i + len] ^ sum[i]) == 0){
                    return len;
                }
            }
        }
        return 0;
    }
    public boolean isVowel(char c){
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
}

image.png

class Solution {
    public int findTheLongestSubstring(String s) {
        int n = s.length();
        Map<Integer, Integer> stateToIndex = new HashMap<>();
        stateToIndex.put(0, -1);
        int state = 0;
        int res = 0;
        for (int i = 0; i < n; i++){
            if (isVowel(s.charAt(i))){
                int bit = 1 << (s.charAt(i) - 'a');
                state = state ^ bit;
            }
            int l = stateToIndex.getOrDefault(state ^ 0, n);          
            res = Math.max(res, i - l);
            if (!stateToIndex.containsKey(state)){
                stateToIndex.put(state, i);
            }                 
        }
        return res;
    }
    public boolean isVowel(char c){
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
}


image.png

找出最长的超赞子字符串【LC1542】

给你一个字符串 s 。请返回 s 中最长的 超赞子字符串 的长度。

「超赞子字符串」需满足满足下述两个条件:

  • 该字符串是 s 的一个非空子字符串
  • 进行任意次数的字符交换后,该字符串可以变成一个回文字符串
前缀和+位运算+枚举【超时】
class Solution {
    public int longestAwesome(String s) {
        int n = s.length();
        int[] sum = new int[n + 1];
        for (int i = 0; i < n; i++){
            int bit = 1 << (s.charAt(i) - '0');
            sum[i + 1] = sum[i] ^ bit;
        }
        for (int len = n; len > 0; len--){
            for (int i = 0; i + len <= n; i++){
                int odd = Integer.bitCount(sum[i + len] ^ sum[i]);
                if (odd <= 1 ){
                    return len;
                }
            }
        }
        return 0;
    }
}

image.png

class Solution {
    public int longestAwesome(String s) {
        // 最多有一个字符出现次数为奇数
        // 最多只有一位不一样,更改每一位判断哈希表是否出现过
        int n = s.length();
        Map<Integer, Integer> stateToIndex = new HashMap<>();
        stateToIndex.put(0, -1);
        int state = 0;
        int res = 0;
        for (int i = 0; i < n; i++){
            int bit = 1 << (s.charAt(i) - '0');
            state = state ^ bit;
            int l = stateToIndex.getOrDefault(state, n);
            res = Math.max(res, i - l);
            // 将某一位设置为相反
            for (int j = 0; j <= 9; j++){
                int b = (state >> j) & 1;// 第j个数位的值
                int target = state;
                if (b == 1){// 将第j位设置为0
                    target &= ~(1 << j);
                }else{// 将第i位设置为1
                    target |= 1 << j;
                }
                l = stateToIndex.getOrDefault(target, n);
                res = Math.max(res, i - l);
            }
            if (!stateToIndex.containsKey(state)){
                stateToIndex.put(state, i);
            }          
        }
        return res;
    }
}

image.png

最美子字符串的数目【LC1915】

如果某个字符串中 至多一个 字母出现 奇数 次,则称其为 最美 字符串。

  • 例如,"ccjjc""abab" 都是最美字符串,但 "ab" 不是。

给你一个字符串 word ,该字符串由前十个小写英文字母组成('a''j')。请你返回 word最美非空子字符串 的数目*。如果同样的子字符串在 word 中出现多次,那么应当对 每次出现 分别计数。*

子字符串 是字符串中的一个连续字符序列。

前缀和+位运算+哈希表

将哈希表记录坐标改为记录数目,其他同上

class Solution {
    public long wonderfulSubstrings(String word) {
        // 只有一个字母出现奇数次->异或后最多只有一位为1->target只有一位和state不同
        int n = word.length();
        Map<Integer, Integer> stateToCount = new HashMap<>();
        stateToCount.put(0, 1);
        int state = 0;
        long res = 0L;
        for (int i = 0; i < n; i++){
            int bit = (1 << (word.charAt(i) - 'a'));
            state = state ^ bit;
            res += stateToCount.getOrDefault(state, 0);
            for (int j = 0; j <= 'j' - 'a'; j++){
                int b = (state >> j) & 1;// 第j个数位的值
                int target = state;
                if (b == 1){// 将第j位设置为0
                    target &= ~(1 << j);
                }else{// 将第i位设置为1
                    target |= 1 << j;
                }
                res += stateToCount.getOrDefault(target, 0);
            }
            stateToCount.put(state, stateToCount.getOrDefault(state, 0) + 1);
        }
        return res;
    }
}

image.png

class Solution {
    public long wonderfulSubstrings(String word) {
        // 只有一个字母出现奇数次->异或后最多只有一位为1->target只有一位和state不同
        int n = word.length();
        Map<Integer, Integer> stateToCount = new HashMap<>();
        stateToCount.put(0, 1);
        int state = 0;
        long res = 0L;
        for (int i = 0; i < n; i++){
            int bit = (1 << (word.charAt(i) - 'a'));
            state = state ^ bit;
            res += stateToCount.getOrDefault(state, 0);
            for (int j = 1; j < 1024; j <<= 1){
                res += stateToCount.getOrDefault(state ^ j, 0);
            }
            stateToCount.put(state, stateToCount.getOrDefault(state, 0) + 1);
        }
        return res;
    }
}


目录
相关文章
|
1月前
|
存储 索引
|
1月前
leetcode-434:字符串中的单词数
leetcode-434:字符串中的单词数
31 1
|
1月前
1456.定长子串中元音的最大数目
1456.定长子串中元音的最大数目
19 1
|
1月前
|
算法 测试技术 C#
【线段树】2213. 由单个字符重复的最长子字符串
【线段树】2213. 由单个字符重复的最长子字符串
|
1月前
|
索引
leetcode-1624:两个相同字符之间的最长子字符串
leetcode-1624:两个相同字符之间的最长子字符串
25 0
|
1月前
|
存储 算法 程序员
【算法训练-字符串 一】【子串问题】最长无重复子串、最长回文子串、最长公共前缀
【算法训练-字符串 一】【子串问题】最长无重复子串、最长回文子串、最长公共前缀
48 0
|
11月前
|
算法 C语言 C++
【前缀和】1456.定长子串中元音的最大数目
本篇将学习前缀和OJ题定长子串中元音的最大数目相关知识。
55 1
剑指offer 49. 最长不含重复字符的子字符串
剑指offer 49. 最长不含重复字符的子字符串
50 0
每日三题-无重复字符的最长子串、最长连续序列、找到字符串中所有字母异位词
每日三题 无重复字符的最长子串 最长连续序列 找到字符串中所有字母异位词
79 1
每日三题-无重复字符的最长子串、最长连续序列、找到字符串中所有字母异位词
|
Python
LeetCode 1456. 定长子串中元音的最大数目
给你字符串 s 和整数 k 。
132 0