题目链接:https://leetcode-cn.com/problems/find-longest-awesome-substring/
- 这道题感觉和前缀和的巧妙运用这两个题目有点像,也是子串(子数组)满足某个条件,这里是子串里至多有一个数字出现过奇数次,其余都是偶数次,然后求符合这个条件的子串的最大长度。
- 首先思考下暴力该怎么做:遍历每个数字,然后统计每个数字出现的次数,对于符合条件的子串必定是这样 s [ 0... i − 1 ] s [ i . . . j ] s[0...i-1]s[i...j] s[0...i−1]s[i...j] 其中 s [ i . . . j ] s[i...j] s[i...j]中出现奇数次的数字至多有一个,那么我们在每个下标处都要保存下从 [ 0 , i ] [0,i] [0,i]区间内0~9出现了多少次,然后再往前遍历,看区间[j,i]中是否有符合题意的子串,有就更新答案。
int longestAwesome(string s) {
static const int N = 1e5 + 5;
int cnt[N][10];
memset(cnt, 0, sizeof(cnt));
int ans = 0;
for (int i = 1; i <= s.size(); ++i){
for (int k = 0; k < 10; ++k) cnt[i][k] = cnt[i - 1][k];
++cnt[i][s[i - 1] - '0'];
for (int j = 1; j <= i; ++j){
int odd = 0;
for (int k = 0; k < 10; ++k){
if ((cnt[i][k] - cnt[j][k]) % 2) odd += 1;
}
if (odd <= 1) ans = max(ans, i - j);
}
}
return ans;
}
- 很明显TLE,数据为 1 e 5 1e5 1e5。
- 其实,我们只需要统计每个数字出现的奇偶性就可以了。压缩状态:以0和1分别表示奇数次和偶数次,因为一共只有10个数,所以只有210=1024 个状态。
- 算法:用一个变量
b
i
t
s
bits
bits(前缀和)去不停地迭代当前的状态:
bits ^= c
(将某一位置成相反的),并将其状态和对应下标存入哈希表中。 - 1.如果之前有某个状态 s t st st和现在一样,说明从 s t st st对应的位置 j j j后一直到 i i i:区间 [ j + 1 , i ] [j+1,i] [j+1,i]中所有数字出现了偶数次。如果曾经出现过这个状态,更新答案。
- 2.那么还有某个数字出现了奇数次的状态,就需要循环0~10,依次将某个位置上置成相反的形成新状态
oddst:bits ^ (1 << c)
。如果之前出现过 o d d s t oddst oddst,那么说明区间 [ j + 1 , i ] [j+1,i] [j+1,i]中所有除了c这个数外其余都出现了偶数次。如果曾经出现过这个状态,更新答案。
(后面赋上 d p dp dp写法)
哈希AC代码:
class Solution {
private:
static const int N = 1050;
int dp[N];
unordered_map<int, int> rec;
public:
int longestAwesome(string s) {
//最多有一个字符出现奇数次
int n = s.length();
int bits = 0, ans = 0;
rec.clear();
rec[0] = -1;
for (int i = 0; i < n; ++i){
int c = s[i] - '0';
bits ^= (1 << c);
if (rec.count(bits)) ans = max(ans, i - rec[bits]);
else rec.insert(make_pair(bits, i));
for (int j = 0; j < 10; ++j){
int oddst = bits ^ (1 << j);
if (rec.count(oddst)) ans = max(ans, i - rec[oddst]);
}
}
return ans;
}
};
- 因为其实只有1024个状态,其实只需要开个状态数组记录这些状态即可。哈希映射的是下标,那么状态数组存的就是对应状态的下标了。这也让我明白了dp数组的含义:其实就是状态罢了。
- 1.状态定义: d p ( i ) dp(i) dp(i)表示状态为 i i i的对应下标。
- 2.状态转移:这里其实没有什么状态转移,每个状态存的都是对应状态最左边的那个下标,只不过要更新当前状态对应的最左边的下标。这里更确切地是答案统计,具体和哈希做法类似。
- 3.初始化:因为哈希里是通过查找,而静态数组没法这么做,只能将dp数组初始化成一个很大的数字(不合法)。这样答案统计的时候就直接过滤掉。 d p [ 0 ] = − 1 dp[0]=-1 dp[0]=−1,所有数字都出现了0次(偶数次)的最左边下标是-1(因为这时候一个数字都没遍历)。
class Solution {
private:
static const int N = 1050;
int dp[N];
public:
int longestAwesome(string s) {
//最多有一个字符出现奇数次
int n = s.length();
int bits = 0, ans = 0;
for (int i = 0; i < N; ++i) dp[i] = n + 1;
dp[0] = -1;
for (int i = 0; i < n; ++i){
int c = s[i] - '0';
bits ^= (1 << c);
dp[bits] = min(dp[bits], i);
//出现偶数次
ans = max(ans, i - dp[bits]);
//某个出现奇数次 其余为偶数次
for (int j = 0; j < 10; ++j)
ans = max(ans, i - dp[bits ^ (1 << j)]);
}
return ans;
}
};