在力扣阅读了大佬灵茶山艾符对“找出最长的超赞子字符串”算法题的题解后的个人理解。

笔者小白一个,本文章是笔者在膜拜大佬灵茶山艾符题解,并依照大佬思路操作一遍后,依照个人理解对解题过程进行再复述。写文章的目的在于加深笔者对题目的理解,算是对自己的一个锻炼了。

一. 题目描述

二. 个人解题的理解

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)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值