笔者小白一个,本文章是笔者在膜拜大佬灵茶山艾符题解,并依照大佬思路操作一遍后,依照个人理解对解题过程进行再复述。写文章的目的在于加深笔者对题目的理解,算是对自己的一个锻炼了。
一. 题目描述
二. 个人解题的理解
1.设置pre。
(1)pre[i]的值的二进制形式表示字符串s的下标范围在[0, i]的子串中 0 ~ 9 分别出现的次数的奇偶性,分别用pre的二进制形式中的0 ~ 9位来表示,出现次数为奇数则用1表示,出现次数为偶数用0表示。
(2)pre[ [j + 1, i] ]的值的二进制形式表示字符串s的下标范围在[j + 1, i]的子串中 0 ~ 9 分别出现的次数的奇偶性,分别用pre的二进制形式中的0 ~ 9位来表示,出现次数为奇数则用1表示,出现次数为偶数用0表示。
2.根据1.的定义,可推出pre[i] ^ pre[j](^ 表示按位异或操作)的二进制值表示字符串s的下标范围在[j + 1, i]的子串中 0 ~ 9 分别出现的次数的奇偶性。
以字符串“3242415”为例,由pre的定义得:
pre[6] = 0b0000101010 ,pre[3] = 0b0000011000(这里仅显示0到9位)。
而字符串s的下标范围在[3 + 1, 6]的子串中 0 ~ 9 分别出现的次数的奇偶性pre[ [4, 6] ] = 0b0000110010。
以数字‘4’为例,其在子串[0, 6]内出现次数为2,为偶数,所以在pre[6]二进制值的第四位用0表示;在子串[0, 3]内出现次数为1,为奇数,所以在pre[3]二进制值的第四位用1表示。因为子串[4, 6]相当于子串[0, 6]剔除了[0, 3]的部分,所以‘4’在子串[4, 6]的出现次数为(2 - 1) == 1,为奇数,在pre[ [4, 6] ]的二进值的第四位用1表示。
根据上面的例子可得:
设子串[j + 1, i],设数字num在子串[0, i]中出现次数为n1, 在子串[0, j]中出现次数为n2,在子串[j + 1, i]中出现次数为n3。
(1)如果n1为偶数(pre[i]的第num位为0),n2为奇数(pre[j]的第num位为1)。则n3 == n1 - n2,n3为奇数(pre[ [j + 1, i] ]的第num位为1)。而0 与 1 异或等于 1。
(2)如果n1为偶数(pre[i]的第num位为0),n2为偶数(pre[j]的第num位为0)。则n3 == n1 - n2,n3为偶数(pre[ [j + 1, i] ]的第num位为0)。而0 与 0 异或等于 0。
(3)如果n1为奇数(pre[i]的第num位为1),n2为奇数(pre[j]的第num位为1)。则n3 == n1 - n2,n3为偶数(pre[ [j + 1, i] ]的第num位为0)。而1 与 1 异或等于 0。
(4)如果n1为奇数(pre[i]的第num位为1),n2为偶数(pre[j]的第num位为0)。则n3 == n1 - n2,n3为奇数(pre[ [j + 1, i] ]的第num位为1)。而1 与 0 异或等于 1。
pre[ [j + 1, i] ] = pre[i] ^ pre[j]得证;
3.s的子串为超赞子字符串的两种情形:
(1)子串长度为偶数:
此情形下,子串[j + 1, i]中 0 ~ 9 的出现次数均为偶数,pre[ [j + 1, i] ]的二进制数中各位均为0,所以pre[ [j + 1], i] == 0 == pre[i] ^ pre[j] ——>pre[i] ^ 0 == pre[j]。
(2)子串长度为奇数:
此情形下,则 0 ~ 9 这9个数字有且仅有一个在子串[j + 1, i]中出现次数为奇数,其他数字字符均为偶数,即pre[ [j + 1, i] ]的二进制数的 0 ~ 9 位中有且仅有一位为1,其余的各位均为0。所以pre[ [j + 1, i] ] == 2^k(k >= 0 && k <= 9) == pre[i] ^ pre[j](其中2^k表示2的k次方,pre[ [j + 1, i] ]的第k位为1)——>pre[i] ^ 2^k == pre[j]。
4.找到最长超赞子字符串的长度
即在字符串s中找到 (i - j) 最大的超赞子字符串[j + 1, i]。
三.代码实现(Java)
1.建立一个数组pro,用于记录pre[i]对应的下标i。长度设置为 2^10,因为要包括pre可能的值,而pre的可能取值为(1 ~ 0b1111111111)。同时使pro[0]等于-1,因为pre[i] == 0的情况不可能出现,如果不设置为-1,则其默认值为0(即表示pre[0] == 0),会影响后续计算。 且把pro数组中除pro[0]以外的所有元素的初试值设为n,表示我们现在还不知道pro[pre[i]]所对应的下标 i 是多少。代码如下:
int[] pro = new int[1 << 10];
Arrays.fill(pro, s.length());
pro[0] = -1;
2.设置pre,遍历s字符串, 进行如下操作:
int pre = 0;
for (int i = 0; i < s.length(); i++) {
pre ^= 1 << i;
//当前的pre表示pre[i],即字符串s的下标范围在[0, i]的子串中 0 ~ 9 分别出现的次数的奇偶性
}
上面代码赋值符的操作右边形成的数num的二进制形式中只有一位为1,
即要添加的数字字符所对应的位,其余的均为0。
故:
如果num二进制形式中某位为0,则表明添加的不是该位所对应的数字
字符,pre与之相应的位保持不变。
如果为1,则同理可得,pre需要对相应的位进行奇偶状态更新,奇变
偶(1 -> 0)或者偶变奇(0 -> 1)。
讨论:
1.如果pre某位原来为1,与0异或还是1,与1异或变为0;
2.如果pre某位原来为0,与0异或还是0,与1异或变为1.
3.设置变量ans,表示当前已知最长超赞子字符串的长度,把s子字符串遍历完后ans的值便是最长超赞子字符串的长度,然后在循环中作如下操作:
int ans = 0;
int pre = 0;
for (int i = 0; i < s.length(); i++) {
pre ^= 1 << i;
//将ans与以 i 为右闭端点,且长度为偶数的最长超赞子字符串进行比较,如果后者值大于前者,
//把ans的值更新为后者。pro[pre ^ 0]返回的是以 i 为右闭端点,且长度为偶数的最长超赞子字
//符串的左开端点,具体原理参照二.3.(1)。
ans = Math.max(ans, i - pro[pre ^ 0]);
//将ans与以 i 为右闭端点,且长度为奇数的最长超赞子字符串进行比较,如果后者大于前者,
//把ans的值更新为后者。pro[pre ^ (1 << k)](相当于pro[pre ^ 2^k]),返回的是以 i 为右闭端点,
//且长度为奇数的最长超赞子字符串的左开端点,具体原理参照二.3.(2)。
//由于我们具体不知道 k 具体是多少,所以直接将 k 从 0 到 9 依次遍历,找到最大值即可。
for (int k = 0; k < 10; k++) {
ans = Math.max(ans, i - pro[pre ^ (1 << k)]);
}
//如果我们还不知道pro[pre[i]]的值 i (即pro[pre[i]] == s.length()),则将当前的 i 赋值给它。
//切莫把判断条件去掉,假设 n 和 m 是s字符串的两个下标,且n < m,可能会出现pre[n] == //pre[m]的情况如果保留判断条件,最后记录在pro[pre[k]]中的 k 是最小的下标n,保证上面两
//块代码中得到得到的pro[pre[j]]的值最小,使得计算出来的超赞子字符串长度为最长。如果
//去掉判断条件,则计算得到的超赞子字符串的长度始终不是最大值,出现错误。
if (pro[pre] == s.length()) {
pro[pre] == i;
}
}
四.代码(java)