leetcode1542. 找出最长的超赞子字符串
给你一个字符串 s
。请返回 s
中最长的 超赞子字符串 的长度。
「超赞子字符串」需满足满足下述两个条件:
- 该字符串是
s
的一个非空子字符串 - 进行任意次数的字符交换后,该字符串可以变成一个回文字符串
示例 1:
输入:s = "3242415"
输出:5
解释:"24241" 是最长的超赞子字符串,交换其中的字符后,可以得到回文 "24142"
示例 2:
输入:s = "12345678"
输出:1
示例 3:
输入:s = "213123"
输出:6
解释:"213123" 是最长的超赞子字符串,交换其中的字符后,可以得到回文 "231132"
示例 4:
输入:s = "00"
输出:2
提示:
1 <= s.length <= 10^5
s
仅由数字组成
方法:哈希表+状态压缩+前缀和
思路:
首先考虑回文字符串,任何一个可以变成回文字符串的字符串,它的所有字符中,只有0个或1个字符是奇数个,其他均为偶数个。
因为,我们一共只有0-9,10中字符,且我们只关心每个字符个数的奇偶性,因此我们使用状态压缩的方法,使用10位的二进制数,每一位表示对应数字出现的奇偶次数,1为奇数次,0为偶数次。
朴素的想法是,求前缀和,从0开始对每个数字求异或(数字i对应的二进制为1<<i)得到前缀和。如果前缀和只有一位是1,其他都是0,那么就符合要求。但是这样的方法,需要考虑到所有的起止位置,计算前缀和,那么时间复杂度为O(N ^ 2),会超时,所以需要进行优化。
一共10位的二进制,因此状态数一共有2 ^ 10=1024种,我们使用哈希表position,position[stat]
表示stat这种状态第一次出现的下标位置。对于stat = 0,我们的下标设为-1,这样当下一次遇到stat=0时,从头开始到现在为超赞子字符串,长度即为该下标+1。
我们从头开始遍历字符串,下标为i,使用temp来计算前缀和,根据异或的性质以及回文字符串的性质,如果与此时的前缀和只有一位不同的前缀和之前出现过,那么从它出现过的位置到此时的位置的子串为超赞字符串(即这段子串中,只有一个字符出现奇数次)。
- 因此我们对temp改变某一位(与1<<j异或,j取值0-9),如果这个结果存在与position中,那么此时i-position[tempp]即为一个超赞子串的长度。
- 下面继续判断,如果此时的temp之前没出现过,那么position[temp] = i
- 如果此时的temp之前出现过,那么相当于从temp第一次出现的位置到此时的位置为一个超赞字符串(所有的字符出现为偶数次)。
在遍历过程中,不断更新最长的长度res,最后返回即可。
通过这种方法就将时间复杂度降到O(10N),渐进复杂度为O(N)
代码:
Python3:
class Solution:
def longestAwesome(self, s: str) -> int:
res = 1
n = len(s)
# position字典,存放每个前缀和第一次出现的位置,一共可能出现1024种
position = dict()
# 最开始的前缀和0位置置为-1,表示从头开始的超赞子字符串长度
position[0] = -1
temp = 0
# 开始遍历
for i in range(n):
# 求异或的前缀和
temp ^= 1 << (ord(s[i]) - ord('0'))
# 对当前的前缀和,只改变其中一位的值,看这个前缀和之前是否出现过,
# 如果出现过,那么说明从第一次出现的位置,到现在的i位置的子串为超赞子字符串
for j in range(10):
tempp = temp ^ (1<<j)
if tempp in position:
res = max(res,i-position[tempp])
# 如果temp自己没出现过,那么就是第一次出现,更新position
if temp not in position:
position[temp] = i
# 如果temp出现过,那么从它第一次出现到i,这段子串为超赞子字符串
else:
res = max(res,i-position[temp])
return res
cpp:
class Solution {
public:
int longestAwesome(string s) {
int res = 1;
int n = s.size();
// position字典,存放每个前缀和第一次出现的位置,一共可能出现1024种
unordered_map<int,int>position;
// 最开始的前缀和0位置置为-1,表示从头开始的超赞子字符串长度
position[0] = -1;
int temp = 0;
// 开始遍历
for (int i = 0; i < n; ++i){
// 求异或的前缀和
temp ^= 1 << (s[i] - '0');
// 对当前的前缀和,只改变其中一位的值,看这个前缀和之前是否出现过,
// 如果出现过,那么说明从第一次出现的位置,到现在的i位置的子串为超赞子字符串
for (int j = 0; j < 10; ++j){
int tempp = temp ^ (1<<j);
if (position.count(tempp))
res = max(res,i-position[tempp]);
}
// 如果temp自己没出现过,那么就是第一次出现,更新position
if (!position.count(temp)) position[temp] = i;
// 如果temp出现过,那么从它第一次出现到i,这段子串为超赞子字符串
else res = max(res,i-position[temp]);
}
return res;
}
};