1542. 找出最长的超赞子字符串
给你一个字符串 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 <= 105
s 仅由数字组成
- 首先一个不难发现的规律,要能够组成回文串,那不同的字符最多只能有一个,相当于一个字符串中 0~9 每个字符串的个数只能有一个为奇数个,其余都应该为偶数个。由于只有
0~9
这 10 种字符,那么我们可以用一个 10 位二进制数 status 来表示一个字符串的每个字符的奇偶性,0 表示有偶数个,1 表示有奇数个。例如 123 对应的 status 为 0000001110,而 1123 就对应 0000001100。 - 最核心也是最难理解的一点就是,为什么我们可以通过两个 status 来确定是否存在一个超赞子字符串。
- 我们将 00,1122,006677 这种形式称为字符对。
- 假设 t[i] 为 s[0,i] 对应的 status, 如果有 t[i] 和 t[j] 只相差最多一位的奇偶性,那么 s[i,j] 就是超赞子字符串,因为他们相差的的部分是大于等于 0 个字符对,以及 1 个某个字符,这不就符合我们发现的规律吗。例如 123 和 12300,因为他们只相差了偶数个 0,或者 123 和 123001122,相差了偶数个 0、1、2,所以他们的 status 相同,他们之间相差的 00 或是 001122 就超赞的,或者比如 123 和 123001,他们的 status 只相差一位,那么 001 也超赞的。
- 我们从左到右遍历 s,用一个数组 pre 存储第一次出现某个 status 时的下标,如果之后又出现了相同的 status 那么就相当于出现了相同的 status,得到了一次超赞子字符串;或者我们每次遍历时,尝试对 status 的某一位取反得到 t[j],如果真的有这样的 t[j] 就说明可以得到这样的只相差一位的 status,那也计算一次
- 剩下的就是具体实现的细节了,由于 pre 数组存储 status 第一次出现的下标,所以需要初始化成小于 0 的数防止被当成下标,又因为理论上来说空字符串所对应的 status 也是 0,因为空字符串相当于每个字符都是 0 个,即都是偶数个,那自然为 0,所以第一次出现 status 为 0 的下标是 -1,所以我们就初始化 pre[0] 为 -1,其他的可以初始化比如 -2 或者其他负数。
- 剩下的状态压缩,按位取反就是简单的位运算了
-
public int longestAwesome(String s) { int status = 0, res = 0; int[] pre = new int[1 << 10]; Arrays.fill(pre, -2); pre[0] = -1; for(int i = 0; i < s.length(); i++){ status ^= 1 << (s.charAt(i) - '0'); // status 相差 0 位时需要比较一次长度 if(pre[status] != -2)res = Math.max(res, i - pre[status]); else pre[status] = i; // 看有没有和当前 status 只相差 1 位的 status2 for(int j = 0; j < 10; j++){ int status2 = status ^ (1 << j); if(pre[status2] != -2)res = Math.max(res, i - pre[status2]); } } return res; }