1. 题目来源
十分推荐看看官方题解,很清楚。
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]
都可以与它构成方案。假设前面有m
个i
满足与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;
}
};