题目要求寻找某种特性的子串的数目或者最长长度,而这种特性与字符出现次数的奇偶性相关,那么可以使用前缀和+异或+哈希表解决
给你一个字符串
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
,可以认为每次检测都是独立的)
思路
由于可以将子串重新排列,那么重新排列后能否构成回文串取决于子串中奇数个字母的数目 n u m num num
如果暴力求出子串中奇数个字母的数目,时间复杂度为 O ( n 2 ) \mathcal{O}(n^2) O(n2)会超时,优化思路为使用前缀和数组预处理每个字母的数量,那么能快算求出子串中奇数个字母的数目
实现
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;
}
}
思路
由于只关心每种字母出现次数的奇偶性,所以不需要在前缀和中存储每种字母的出现次数,只需要保存每种字母出现次数的奇偶性。
为方便计算,用 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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Tips:异或可以视作「不进位加法(减法)」或者「模 222 剩余系中的加法(减法)」,所以也常常用来解决一些和奇偶性有关的问题。
给你一个字符串
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';
}
}
优化:
通过哈希表进行优化,寻找以某个字符为结尾的,符合题意的最长子字符串的长度,假设当前子串 s [ 0 , r ] s[0,r] s[0,r]对应的元音字母奇偶性为state
,如果子串 s [ l , r ] s[l,r] s[l,r]符合要求,子串 s [ 0 , l ] s[0,l] s[0,l]对应的状态码为$state \oplus 0 $。因此使用哈希表记录state的最左位置
实现
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';
}
}
给你一个字符串
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;
}
}
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;
}
}
如果某个字符串中 至多一个 字母出现 奇数 次,则称其为 最美 字符串。
- 例如,
"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;
}
}
复杂度
也可以枚举第 i i i位为奇数个时,求出是否有相应的state
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;
}
}