c语言对字符串进行前缀重叠判断_leetcode第32双周赛第四题leetcode1542. 找出最长的超赞子字符串

leetcode1542. 找出最长的超赞子字符串

给你一个字符串 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 <= 10^5
  • s 仅由数字组成

方法:哈希表+状态压缩+前缀和

思路:

首先考虑回文字符串,任何一个可以变成回文字符串的字符串,它的所有字符中,只有0个或1个字符是奇数个,其他均为偶数个。

因为,我们一共只有0-9,10中字符,且我们只关心每个字符个数的奇偶性,因此我们使用状态压缩的方法,使用10位的二进制数,每一位表示对应数字出现的奇偶次数,1为奇数次,0为偶数次。

朴素的想法是,求前缀和,从0开始对每个数字求异或(数字i对应的二进制为1<<i)得到前缀和。如果前缀和只有一位是1,其他都是0,那么就符合要求。但是这样的方法,需要考虑到所有的起止位置,计算前缀和,那么时间复杂度为O(N ^ 2),会超时,所以需要进行优化。

一共10位的二进制,因此状态数一共有2 ^ 10=1024种,我们使用哈希表position,position[stat]表示stat这种状态第一次出现的下标位置。对于stat = 0,我们的下标设为-1,这样当下一次遇到stat=0时,从头开始到现在为超赞子字符串,长度即为该下标+1。

我们从头开始遍历字符串,下标为i,使用temp来计算前缀和,根据异或的性质以及回文字符串的性质,如果与此时的前缀和只有一位不同的前缀和之前出现过,那么从它出现过的位置到此时的位置的子串为超赞字符串(即这段子串中,只有一个字符出现奇数次)。

  • 因此我们对temp改变某一位(与1<<j异或,j取值0-9),如果这个结果存在与position中,那么此时i-position[tempp]即为一个超赞子串的长度。
  • 下面继续判断,如果此时的temp之前没出现过,那么position[temp] = i
  • 如果此时的temp之前出现过,那么相当于从temp第一次出现的位置到此时的位置为一个超赞字符串(所有的字符出现为偶数次)。

在遍历过程中,不断更新最长的长度res,最后返回即可。

通过这种方法就将时间复杂度降到O(10N),渐进复杂度为O(N)

代码:

Python3:

class Solution:
    def longestAwesome(self, s: str) -> int:
        res = 1
        n = len(s)
        # position字典,存放每个前缀和第一次出现的位置,一共可能出现1024种
        position = dict()
        # 最开始的前缀和0位置置为-1,表示从头开始的超赞子字符串长度
        position[0] = -1
        temp = 0
        # 开始遍历
        for i in range(n):
            # 求异或的前缀和
            temp ^= 1 << (ord(s[i]) - ord('0'))
            # 对当前的前缀和,只改变其中一位的值,看这个前缀和之前是否出现过,
            # 如果出现过,那么说明从第一次出现的位置,到现在的i位置的子串为超赞子字符串
            for j in range(10):
                tempp = temp ^ (1<<j)
                if tempp in position:
                    res = max(res,i-position[tempp])
            # 如果temp自己没出现过,那么就是第一次出现,更新position
            if temp not in position:
                position[temp] = i
            # 如果temp出现过,那么从它第一次出现到i,这段子串为超赞子字符串
            else:
                res = max(res,i-position[temp])
        return res

cpp:

class Solution {
public:
    int longestAwesome(string s) {
        int res = 1;
        int n = s.size();
        // position字典,存放每个前缀和第一次出现的位置,一共可能出现1024种
        unordered_map<int,int>position;
        // 最开始的前缀和0位置置为-1,表示从头开始的超赞子字符串长度
        position[0] = -1;
        int temp = 0;
        // 开始遍历
        for (int i = 0; i < n; ++i){
            // 求异或的前缀和
            temp ^= 1 << (s[i] - '0');
            // 对当前的前缀和,只改变其中一位的值,看这个前缀和之前是否出现过,
            // 如果出现过,那么说明从第一次出现的位置,到现在的i位置的子串为超赞子字符串
            for (int j = 0; j < 10; ++j){
                int tempp = temp ^ (1<<j);
                if (position.count(tempp))
                    res = max(res,i-position[tempp]);
            }   
            // 如果temp自己没出现过,那么就是第一次出现,更新position
            if (!position.count(temp)) position[temp] = i;
            // 如果temp出现过,那么从它第一次出现到i,这段子串为超赞子字符串
            else res = max(res,i-position[temp]);
        }
        return res;

    }
};

结果:

f8b29ca1d31a9dce0172e41b6f672ae8.png

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值