状态压缩:LeetCode 1542

题目链接:https://leetcode-cn.com/problems/find-longest-awesome-substring/
在这里插入图片描述
在这里插入图片描述

  • 这道题感觉和前缀和的巧妙运用这两个题目有点像,也是子串(子数组)满足某个条件,这里是子串里至多有一个数字出现过奇数次,其余都是偶数次,然后求符合这个条件的子串的最大长度。
  • 首先思考下暴力该怎么做:遍历每个数字,然后统计每个数字出现的次数,对于符合条件的子串必定是这样 s [ 0... i − 1 ] s [ i . . . j ] s[0...i-1]s[i...j] s[0...i1]s[i...j] 其中 s [ i . . . j ] s[i...j] s[i...j]中出现奇数次的数字至多有一个,那么我们在每个下标处都要保存下从 [ 0 , i ] [0,i] [0,i]区间内0~9出现了多少次,然后再往前遍历,看区间[j,i]中是否有符合题意的子串,有就更新答案。
int longestAwesome(string s) {
        static const int N = 1e5 + 5;
        int cnt[N][10];
        memset(cnt, 0, sizeof(cnt));
        int ans = 0;
        for (int i = 1; i <= s.size(); ++i){
            for (int k = 0; k < 10; ++k) cnt[i][k] = cnt[i - 1][k];
            ++cnt[i][s[i - 1] - '0'];
            
            for (int j = 1; j <= i; ++j){
                int odd = 0;
                for (int k = 0; k < 10; ++k){
                    if ((cnt[i][k] - cnt[j][k]) % 2) odd += 1;
                }
                if (odd <= 1) ans = max(ans, i - j);
            }
        }
        return ans;
    }
  • 很明显TLE,数据为 1 e 5 1e5 1e5
  • 其实,我们只需要统计每个数字出现的奇偶性就可以了。压缩状态:以0和1分别表示奇数次和偶数次,因为一共只有10个数,所以只有210=1024 个状态。
  • 算法:用一个变量 b i t s bits bits(前缀和)去不停地迭代当前的状态:bits ^= c(将某一位置成相反的),并将其状态和对应下标存入哈希表中。
  • 1.如果之前有某个状态 s t st st和现在一样,说明从 s t st st对应的位置 j j j后一直到 i i i:区间 [ j + 1 , i ] [j+1,i] [j+1,i]中所有数字出现了偶数次。如果曾经出现过这个状态,更新答案。
  • 2.那么还有某个数字出现了奇数次的状态,就需要循环0~10,依次将某个位置上置成相反的形成新状态oddst:bits ^ (1 << c)。如果之前出现过 o d d s t oddst oddst,那么说明区间 [ j + 1 , i ] [j+1,i] [j+1,i]中所有除了c这个数外其余都出现了偶数次。如果曾经出现过这个状态,更新答案。
    (后面赋上 d p dp dp写法)

哈希AC代码:

class Solution {
private:
    static const int N = 1050;
    int dp[N];
    unordered_map<int, int> rec;
public:
    int longestAwesome(string s) {
        //最多有一个字符出现奇数次
        int n = s.length();
        int bits = 0, ans = 0;
        rec.clear();
        rec[0] = -1;
        for (int i = 0; i < n; ++i){
            int c = s[i] - '0';
            bits ^= (1 << c);
            if (rec.count(bits)) ans = max(ans, i - rec[bits]);
            else rec.insert(make_pair(bits, i));

            for (int j = 0; j < 10; ++j){
                int oddst = bits ^ (1 << j);
                if (rec.count(oddst)) ans = max(ans, i - rec[oddst]);
            }
        }
        
        return ans;
    }
};
  • 因为其实只有1024个状态,其实只需要开个状态数组记录这些状态即可。哈希映射的是下标,那么状态数组存的就是对应状态的下标了。这也让我明白了dp数组的含义:其实就是状态罢了。
  • 1.状态定义: d p ( i ) dp(i) dp(i)表示状态为 i i i的对应下标。
  • 2.状态转移:这里其实没有什么状态转移,每个状态存的都是对应状态最左边的那个下标,只不过要更新当前状态对应的最左边的下标。这里更确切地是答案统计,具体和哈希做法类似。
  • 3.初始化:因为哈希里是通过查找,而静态数组没法这么做,只能将dp数组初始化成一个很大的数字(不合法)。这样答案统计的时候就直接过滤掉。 d p [ 0 ] = − 1 dp[0]=-1 dp[0]=1,所有数字都出现了0次(偶数次)的最左边下标是-1(因为这时候一个数字都没遍历)。
class Solution {
private:
    static const int N = 1050;
    int dp[N];
public:
    int longestAwesome(string s) {
        //最多有一个字符出现奇数次
        int n = s.length();
        int bits = 0, ans = 0;
        for (int i = 0; i < N; ++i) dp[i] = n + 1;
        dp[0] = -1;

        for (int i = 0; i < n; ++i){
            int c = s[i] - '0';
            bits ^= (1 << c);
            dp[bits] = min(dp[bits], i);
            //出现偶数次
            ans = max(ans, i - dp[bits]);
            //某个出现奇数次 其余为偶数次
            for (int j = 0; j < 10; ++j)
                ans = max(ans, i - dp[bits ^ (1 << j)]);
        }

        return ans;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值