[M位运算] lc1442. 形成两个异或相等数组的三元组数目(位运算+异或+算法优化+边界问题+好题)

1. 题目来源

链接:1442. 形成两个异或相等数组的三元组数目

十分推荐看看官方题解,很清楚。

2. 题目解析

很不错的一道题目,三重优化…我在第二层,没去往第三层…

思路:

  • 方法一: 很明显使用异或前缀和,三重循环枚举三段分界点即可。时间复杂度 O ( n 3 ) O(n^3) O(n3)
  • 方法二: 由于本题的特殊的分段性,a = s[j - 1] ^ s[i - 1]; b = s[k] ^ s[j - 1]; 那么给 a、b 再同时异或一个 s[j - 1],就有 a=b 等价于 s[i-1]=s[k],故中间的 j 是不需要循环枚举的,即 j = [i + 1, k] 之间的方案都是可取的。时间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 方法三: 进而可以发现,顺序遍历时针对一个 k,它前面的 i 只要满足 s[k]=s[i-1] 都可以与它构成方案。假设前面有 mi 满足与 k 异或值相等,那么方案数就等于 (k - i1) + (k-i2) + ...+(k-im)=mk-(i1+i2+...+im),即下标之和。故用 k 遍历 s 这个异或前缀和数组的时候,使用两个哈希表维护 s[k] 出现的次数,即 s[k] 值的下标和。遇到 s[k+1] 时,直接查表即可。

k 下标从 1 开始时,若满足 s[k]=s[i-1] 的话,也要同时满足 k>i,故在 方法三中,往哈希表进行插入、查找的过程中,也要满足这一点,要有一个顿挫在里面。查找的 s[k],要与哈希表中的元素至少中间间隔一个元素,即 s[k] 作为查找元素时,s[k-1] 其实表内是不应该存在的,应该是待插入状态,表内应该是 s[1~k-2] 这些元素,是 i 的可选值。这一点需要注意,不应该是查找 k ,紧接着下来就插入 k


O ( n 3 ) O(n^3) O(n3):

class Solution {
public:
    int countTriplets(vector<int>& arr) {
        int n = arr.size();
        vector<int> s(n + 1);
        for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] ^ arr[i - 1];

        int cnt = 0;
        for (int i = 1; i <= n; i ++ ) 
            for (int j = i + 1; j <= n; j ++ ) 
                for (int k = j; k <= n; k ++ ) {
                    int a = s[j - 1] ^ s[i - 1];
                    int b = s[k] ^ s[j - 1];
                    if (a == b) cnt ++ ;
                }
        
        return cnt;
    }
};

O ( n 2 ) O(n^2) O(n2):

在此,是拿 s[k]==s[i-1] 进行判断的,注意 k>i,在 [i+1, k] 之间的所有 j 都可以成为答案,因为 j 要严格大于 i,但是可以等于 j,即 j 的数量为 k-i

class Solution {
public:
    int countTriplets(vector<int>& arr) {
        int n = arr.size();
        vector<int> s(n + 1);
        for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] ^ arr[i - 1];

        int cnt = 0;
        for (int i = 1; i <= n; i ++ ) 
                for (int k = i + 1; k <= n; k ++ ) {
                    if (s[i - 1] == s[k]) 
                        cnt += k - i;
                }
        
        return cnt;
    }
};

O ( n ) O(n) O(n)

复制上面的话:

k 下标从 1 开始时,若满足 s[k]=s[i-1] 的话,也要同时满足 k>i,故在 方法三中,往哈希表进行插入、查找的过程中,也要满足这一点,要有一个顿挫在里面

查找的 s[k],要与哈希表中的元素至少中间间隔一个元素,即 s[k] 作为查找元素时,s[k-1] 其实表内是不应该存在的,应该是待插入状态,表内应该是 s[1~k-2] 这些元素,是 i 的可选值。这一点需要注意,不应该是查找 k ,紧接着下来就插入 k。这样是错误的。

应该是查找 k,插入的是 k-1

其实在此的 cnt[s[k]] 对应的是 s[i-1]=s[k] 的个数,其实中间合法的 j 就是方案的数量,即 [i+1, k],这一段是合法的,故 k-(i+1) 就是中间的方案数,简单转化即为 (k-1-i),故为 m*(k-1) - idx[s[k]]

为了保持一致性,idx[s[k-1]] 存的 s[k-1] 值相等的下标和也是 k-1,所以 idx[s[k]] 就是下标的和

所以,应该是 ans += cnt[s[k]] * (k - 1) - idx[s[k]];,而不是 ans += cnt[s[k]] * k - idx[s[k]];

细节有点多,把人整晕了…还是公式没有推导的清楚的结果…

class Solution {
public:
    int countTriplets(vector<int>& arr) {
        int n = arr.size();
        vector<int> s(n + 1);
        for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] ^ arr[i - 1];

        unordered_map<int, int> cnt, idx;
        int ans = 0;
        for (int k = 1; k <= n; k ++ ) {
            if (cnt.count(s[k])) ans += cnt[s[k]] * (k - 1) - idx[s[k]];
            cnt[s[k - 1]] ++ ;
            idx[s[k - 1]] += k - 1;
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值