原贴地址:http://blog.leanote.com/post/dawnmagnet/74b9cba6cc8a
题目
思路分析
又是一道异或的题目。
我们再强调一下,异或的最重要的性质就是两个相同的数字异或是0
接下来看一下我们的这个数据量,不太适合暴力,如果按照题目的说法对(i, j, k)三元组进行遍历,复杂度就是 O ( n 3 ) O(n^3) O(n3),达到了 30 0 3 = 27000000 300^3=27000000 3003=27000000复杂度,这种已经很大了,很危险,很有可能过不了。这也是对于所有题目的一个小提示:如果复杂度达到 1 0 9 10^9 109,就过不了了。
这个题目既然如此危险,不到万不得已就不写暴力算法。
此处说明一下优化的手段,一般来说O(n^2)的算法只能往O(n)和O(nlgn)进行优化,但这里我们观察了一下也没什么可以二分的办法,所以我们就用O(n)进行思考,既然是O(n)那么每一次查询都应该是O(1)的复杂度,有可能吗?
这里就要介绍一下前缀和的思想。
我们先思考一个跟本问题很像的一个问题,就是我们将本题中的异或换成加法。我们给入一个查询,查询的是数组中间某一段的和。为了在O(1)的时间应对每次查询,我们新建一个前缀和数组,称为prefix
prefix中的下标 | arr中的下标 |
---|---|
0 | n0 |
1 | n0+n1 |
2 | n0+n1+n2 |
3 | n0+n1+n2+n3 |
4 | n0+n1+n2+n3+n4 |
… | … |
如果我们获得了前缀和数组,对于每次查询都能在O(1)的时间返回,因为如果我们想要获得1至3之间的值(包含3),我们就用**prefix[3]-prefix[0]**就获得了1至3之间的值的和。
我们再仔细思考一下为什么是加法就可以这么做。以及换成异或之后我们应该如何做。
性质|加法+|异或^
–|--
逆操作|减法-|异或^
一顺一逆的结果|0+n-n=0|0nn=0
现在我们可以知道为什么上述操作中我们会用prefix[3]减去prefix[0],因为减法是加法的逆操作。而正因为一顺一逆的结果是0,这个前缀和才有它存在的意义。
我们仿照着加法设计出来我们的异或前缀和表
prefix中的下标 | arr中的下标 |
---|---|
0 | n0 |
1 | n0^n1 |
2 | n0n1n2 |
3 | n0n1n2^n3 |
4 | n0n1n2n3n4 |
… | … |
同样的,我们如果想获得nums中从1异或到3的值,我们只需要获取prefix[3]^prefix[0],也是一个O(1)的操作。
我们再回到题目中的三元组来看一下,如何将前缀和应用到三元组的简化当中。
对于三元组(i, j, k)来说,
a = arr[i] ^ … ^ arr[j - 1]
= prefix[j - 1] ^ prefix[i - 1]
此处使用到了异或的性质
同理b = prefix[k] ^ prefix[j - 1]
所以我们继续推导,当a = b时,我们得出一个惊人的结论,就是prefix[k]=prefix[i-1]!
也就是说,当**prefix[k]=prefix[i-1]**时,我们可以获取k和i中包含的任意一点作为j,并且都是成立的!也就是说,我们确定了一个(k,i)对,就可以让答案加上一个k-i。
经过我们这么一番倒腾,题目啪的一下就变成了另外一个等价的题目。
新题目
给你一个整数数组arr
将其处理成异或前缀和prefix
在prefix数组中寻找下标二元组(i, j), 满足 i < j 并 且 p r e f i x [ i ] = = p r e f i x [ j ] i < j 并且 prefix[i] == prefix[j] i<j并且prefix[i]==prefix[j],每找到一对就让答案加上 j ? i ? 1 j - i - 1 j?i?1,返回最终的答案。
新题目思路分析
经过一番倒腾, O ( n 3 ) O(n^3) O(n3)的复杂度一下子变成了 O ( n 2 ) O(n^2) O(n2),很开心啊!但这个题目,似乎还有优化的空间,因为经过我们处理的新题目看起来就是一个寻找相等数字的事情,似乎(如果加个哈希表),能够处理到 O ( n ) O(n) O(n)的复杂度!啪的一下就站起来了,震惊了!这都可以?
Rust代码
use std::collections::HashMap;
impl Solution {pub fn count_triplets(arr: Vec<i32>) -> i32 {let n = arr.len();let mut cnt = HashMap::new();let mut total = HashMap::new();let mut ans = 0;let mut s = 0;for k in 0..n {let s_nxt = s ^ arr[k];if let Some(&cnt_val) = cnt.get(&s_nxt) {ans += cnt_val * k - *total.entry(s_nxt).or_insert(0);}*cnt.entry(s).or_insert(0) += 1;*total.entry(s).or_insert(0) += k;s = s_nxt;}ans as i32}
}
C++代码
class Solution {
public:int countTriplets(vector<int> &arr) {
int n = arr.size();unordered_map<int, int> cnt, total;int ans = 0, s = 0;for (int k = 0; k < n; ++k) {
int s_nxt = s ^ arr[k];if (cnt.find(s_nxt) != cnt.end()) {
ans += cnt[s_nxt] * k - total[s_nxt];}++cnt[s];total[s] += k;s = s_nxt;}return ans;}
};