Leetcode5337.每个元音包含偶数次的最长子字符串——状态压缩DP

文章目录

引入

在本周的双周赛中,出现了这么一道题

给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = “eleetminicoworoep”
输出:13
解释:最长子字符串是 “leetminicowor” ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = “leetcodeisgreat”
输出:5
解释:最长子字符串是 “leetc” ,其中包含 2 个 e 。
示例 3:
输入:s = “bcbcbc”
输出:6
解释:这个示例中,字符串 “bcbcbc” 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。

一开始我想的是回溯法,也就是在每个字符上选或者不选,但是很明显会又重复计算,需要用一个数组来剪枝。

俗话说得好,能用回溯法的题一定能用动态规划算法。
所以这道题有一个思路清奇的解法,使用状态压缩DP。其实状态压缩DP在我们之前的N皇后问题中也谈到过,当时的用法就足以让人震惊了:使用位运算的左右移动来判断左右是否有人坐。

这里则是用位运算的异或来表示每个元音字母的奇偶出现的次数。

题解

将5个元音字母出现次数的奇偶视为一种状态,一共有 2 5 2^5 25=32种状态。所以不妨使用一个二进制位数为5位的整数代表状态,第0位为1表示a出现奇数次,第一位为1表示e出现奇数次……以此类推。

所以使用异或判断,如果a出现第一次,0^1=1,如果a出现第二次1^1=0。同理,如果e第一次出现是00^10=10,第二次出现则是10^10=00
由此所以我们知道,要想满足题目要求出现的次数都是偶数,那么只有二进制00000才满足要求。

所以现在的问题是,怎么去找这个00000。我们知道,如果子串[0,i]与字串[0,j]状态相同,那么字串[i+1,j]的状态一定是0。

另外需要注意状态0首次出现的位置应该设定为-1。

直接上代码理解:

class Solution {
    public int findTheLongestSubstring(String s) {
        int[] pre=new int[32];
        Arrays.fill(pre,Integer.MAX_VALUE);
        pre[0]=-1;
        int length=s.length();
        int curr=0;
        int ans=0;
        for(int i=0;i<length;i++){
            switch(s.charAt(i)){
                case 'a':curr^=1;break;
                case 'e':curr^=2;break;
                case 'i':curr^=4;break;
                case 'o':curr^=8;break;
                case 'u':curr^=16;break;
                default:break;
            }
            if(pre[curr]==Integer.MAX_VALUE) {
                pre[curr]=i;
            }else {
                ans=Math.max(ans,i-pre[curr]);
            }
        }
        return ans;
    }
}

我们看到关键的其实就是if…else…上的判断。
这个if判断下的pre[curr]=i;有两层含义,第一层是如果判断成立记录下当前状态的位置i,第二层是只记录第一次出现这个状态的位置i,因为我们需要的是最长子字符串。
else下的语句就是记录两次相同状态的情况下的字符串长度。

KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值