0%

统计好三元组

题目地址

难度:

题目描述:

给你一个整数数组 arr ,以及 abc 三个整数。请你统计其中好三元组的数量。

如果三元组 (arr[i], arr[j], arr[k]) 满足下列全部条件,则认为它是一个 好三元组

  • 0 <= i < j < k < arr.length
  • |arr[i] - arr[j]| <= a
  • |arr[j] - arr[k]| <= b
  • |arr[i] - arr[k]| <= c

其中 |x| 表示 x 的绝对值。

返回 好三元组的数量

示例1:

1
2
3
输入:arr = [3,0,1,1,9,7], a = 7, b = 2, c = 3
输出:4
解释:一共有 4 个好三元组:[(3,0,1), (3,0,1), (3,1,1), (0,1,1)] 。

示例2:

1
2
3
输入:arr = [1,1,2,2,3], a = 0, b = 0, c = 1
输出:0
解释:不存在满足所有条件的三元组。

提示:

  • 3 <= arr.length <= 100
  • 0 <= arr[i] <= 1000
  • 0 <= a, b, c <= 1000
🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️解题过程🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️🙋‍♂️
解题过程:

思路:

暴力解法,三层for循环遍历,每层代表一个三元组中的元素。对每组(i,j,k),判断arr[i]、arr[j]、arr[k]是否满足条件。

这种解法时间复杂度是$O(n^3)$,看题解有没有好方法吧。

c++代码:(执行用时64ms,击败14.93%,内存消耗8.6M,击败5.00%)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
int countGoodTriplets(vector<int>& arr, int a, int b, int c) {
//根据题目描述直接模拟即可
int result=0;
for(int i=0;i<arr.size()-2;++i){
for(int j=i+1;j<arr.size()-1;++j){
for(int k=j+1;k<arr.size();++k){
if(abs(arr[i]-arr[j])<=a && abs(arr[j]-arr[k])<=b && abs(arr[i]-arr[k])<=c){
++result;
}
}
}
}
return result;
}
};
💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎官 方 题 解💎💎💎💎💎💎💎💎💎💎💎💎💎💎💎
官方题解:

方法一:枚举
思路与算法

用 $O(n^3)$ 的循环依次枚举所有的 $(i, j, k)$,这里 $0 \leq i < j < k < {\rm arr.length}$,对于每组 (i, j, k)(i,j,k),判断 ${\rm arr}[i]$、${\rm arr}[j]$、${\rm arr}[k]$是否满足条件。

最终统计出所有满足条件的三元组的数量。

c++代码:(执行56ms,击败38.19%,内存8.4M,击败9.00%)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
int countGoodTriplets(vector<int>& arr, int a, int b, int c) {
int n = arr.size(), cnt = 0;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
for (int k = j + 1; k < n; ++k) {
if (abs(arr[i] - arr[j]) <= a && abs(arr[j] - arr[k]) <= b && abs(arr[i] - arr[k]) <= c) {
++cnt;
}
}
}
}
return cnt;
}
};

复杂度分析

  • 时间复杂度:$O(n^3)$,其中 $n$是数组 $\textit{arr}$ 的长度。
  • 空间复杂度:$O(1)$。

方法二:枚举优化

思路与算法

我们考虑 $O(n^2)$ 枚举满足 $|\rm arr[j]-\rm arr[k]|\le b∣$ 的二元组 $(j,k)$,统计这个二元组下有多少 $i$ 满足条件。由题目已知 $i$ 的限制条件为 $|\rm arr[i]-\rm arr[j]|\le a \ \&\&\ |\rm arr[i]-\rm arr[k]|\le c∣$,我们可以拆开绝对值,得到符合条件的值一定是 $[\rm arr[j]-a,\rm arr[j]+a]$ 和 $[\rm arr[k]-c,\rm arr[k]+c]$ 两个区间的交集,我们记为 $[l,r]$。因此,在枚举 $(j,k)$这个二元组的时候,我们只需要快速统计出满足 $i<j$ 且 $\rm arr[i]$的值域范围在 $[l,r]$ 的 $i$ 的个数即可。

很容易想到维护一个 $\rm arr[i]$频次数组的前缀和 $\rm sum$,对于一个二元组 $(j,k)$,我们可以 $O(1)$ 得到答案为 $\rm sum[r]-\rm sum[l-1]$。考虑怎么维护保证当前频次数组存的数的下标符合 $i<j$ 的限制,我们只要从小到大枚举 $j$,每次 $j$移动指针加一的时候,将 $\rm arr[j]$的值更新到 $\rm sum$数组中即可,这样能保证枚举到 $j$的时候 $\rm sum$数组里存的值的下标满足限制。

「将 $\rm arr[j]$ 的值更新到 $\rm sum$数组中」这个操作在本方法中是暴力更新,因为数组的值域上限很小,有能力的读者可以考虑怎么在进一步优化这一部分的复杂度,可以从离散化或者树状数组的角度考虑,这里不再赘述。

c++代码:(执行24ms,击败97.15%,内存9M,击败5.00%)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public:
int countGoodTriplets(vector<int>& arr, int a, int b, int c) {
int ans = 0, n = arr.size();
vector<int> sum(1001, 0);
for (int j = 0; j < n; ++j) {
for (int k = j + 1; k < n; ++k) {
if (abs(arr[j] - arr[k]) <= b) {
int lj = arr[j] - a, rj = arr[j] + a;
int lk = arr[k] - c, rk = arr[k] + c;
int l = max(0, max(lj, lk)), r = min(1000, min(rj, rk));
if (l <= r) {
if (l == 0) {
ans += sum[r];
}
else {
ans += sum[r] - sum[l - 1];
}
}
}
}
for (int k = arr[j]; k <= 1000; ++k) {
++sum[k];
}
}
return ans;
}
};

复杂度分析

时间复杂度:$O(n^2+nS)$,其中$n$ 是数组 $\textit{arr}$的长度,$S$为数组的值域上限,这里为 $1000$。

空间复杂度:$O(S)$。我们需要 $O(S)$ 的空间维护 $\rm arr[i]$频次数组的前缀和。

⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳总 结⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳⏳
总结:

基本上简单题都可以通过暴力解出来,看来是真的😄,官方题解提供了另一种优化的枚举解法,可以学习一波。

------------- THE END! THANKS! -------------