压状、前缀和与子字符串

题目P1542找出最长的超赞子字符串
在这里插入图片描述
首先分析题目,我们需要找到他的子字符串,且能通过任意次数的交换变成一个回文字符串。不考虑顺序仅仅考虑回文字符串的性质:任意一个回文字符串最多有一个数字出现的次数为奇数,其余数字出现的次数必定为偶数
因此我们在判断一个子字符串是否是超赞字符串,仅仅只需要指导各个数字出现的次数的奇偶性就可以,因此我们考虑用到状态压缩,我们用0-1表示数字出现的奇偶,0表示出现奇数次,1表示出现的偶数次。用不大于1024的10位二进制数来表示0-9这10个数字。例如:

举个例子,对于子串 s’= 0366613544。其中 0, 1, 3, 4, 5, 6分别出现了 1, 1, 2, 2, 1, 3次,那么对应的 0-1序列即为0001100011。注意序列的最高位对应字符 9,最低位对应字符 0。即t[0]=1;t[1]=1;t[2]=0…t[6]=1;

由于1 <= s.length <= 10^5,因此我们不能两次循环所有子字符串,因此我们考虑一次循环,j从0到n-1,当我们遍历到位置j时,使j为右端点,用t[j]来表示从0-j每个数字出现次数的二进制数。如果存在某个满足 i<j 的位置 i,并且 s[i:j] 是「超赞子字符串」,那么必然满足下面的条件:

  • t[0:i−1] 与 t[0:j] 最多只有一位不同。特别地,如果 i=0,那么 s[0:i−1] 是一个空子串,t[0:i-1] 为全0序列。

我们考虑到用前缀和的思想来解释:s[i:j] 中某个字符的出现次数,就等于它在 s[0:j] 中的出现次数减去它在s[0:i-1] 中的出现次数。如果我们只考虑出现次数的奇偶性,那么 s[i:j] 中某个字符出现的次数为偶数,当且仅当它在 s[0:j] 和 s[0:i−1] 中出现的次数均为奇数或者均为偶数,也就是 t[0:j] 和 t[0:i−1] 对应的位置相同。因此,如果 s[i:j] 是一个「超赞子字符串」,那么 t[0:j] 和 t[0:i-1] 最多只有一位不同。而那不同的一位,就刚好是回文串中出现次数为唯一奇数的那个数字。

因此在遍历j时,我们考虑用哈希表来记录对应每个j时的二进制数字,他对应的值就是此时遍历的位置j,在每次遍历时,我们都考虑将10个数字逐个翻转来寻找仅有一位二进制数不同的t所对应的键值,并两者做差找到最长的子字符串长度。

需要注意:如果 t[0:j] 已经是哈希映射中的一个键,我们需要忽略这一步操作,这是因为我们要求的是最长的「超赞子字符串」,所以当两个前缀的 0−1 序列相同时,我们应当保留较短的前缀,而忽略较长的前缀。

我们在遍历到j时,需要更新0-1序列,可以采用异或运算0 ^ 1=1、1 ^ 1=0;相当于遍历到一个新的位置,他的数值i就是二进制串的第i位,去异或未更新前的t值,可以达到奇数(1)变偶数(0)、和偶数变奇数的效果。

int longestAwesome(string s){
	int n=s.size();
	unordered_map<int,int>m;
	m[0]=-1; //第0个子串长度为1
	int ans=0;
	int t=0; //记录二进制
	for(int j=0;j<n;j++){
		int dig=s[j]-'0';
		t^=(1<<dig); //更新二进制
		if(m.count(t)!=0){
			ans=max(ans,j-m[t]);
		}
		else m[t]=j;
		for(int k=0;k<10;k++){
			int tt=t^(1<<k); //遍历有一位不同的二进制
			if(m.count(tt)!=0) ans=max(ans,j-m[tt]);
		}
	}
	return ans;
}

记录下这个板子题。
学会了可以尝试一下其他的压状题最美子字符串的数目。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值