阅读视图

发现新文章,点击刷新页面。

[Python3/Java/C++/Go/TypeScript] 一题一解:式子转换 + 哈希表(清晰题解)

方法一:式子转换 + 哈希表

根据题目描述,我们可以得知,对于任意的 $i \lt j$,如果 $j - i \neq \textit{nums}[j] - \textit{nums}[i]$,则 $(i, j)$ 是一个坏数对。

我们可以将式子转换为 $i - \textit{nums}[i] \neq j - \textit{nums}[j]$。这启发我们用哈希表 $cnt$ 来统计 $i - \textit{nums}[i]$ 的出现次数。

遍历数组,对于当前元素 $\textit{nums}[i]$,我们将 $i - cnt[i - \textit{nums}[i]]$ 加到答案中,然后将 $i - \textit{nums}[i]$ 的出现次数加 $1$。

最后,我们返回答案即可。

###python

class Solution:
    def countBadPairs(self, nums: List[int]) -> int:
        cnt = Counter()
        ans = 0
        for i, x in enumerate(nums):
            ans += i - cnt[i - x]
            cnt[i - x] += 1
        return ans

###java

class Solution {
    public long countBadPairs(int[] nums) {
        Map<Integer, Integer> cnt = new HashMap<>();
        long ans = 0;
        for (int i = 0; i < nums.length; ++i) {
            int x = i - nums[i];
            ans += i - cnt.merge(x, 1, Integer::sum) + 1;
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    long long countBadPairs(vector<int>& nums) {
        unordered_map<int, int> cnt;
        long long ans = 0;
        for (int i = 0; i < nums.size(); ++i) {
            int x = i - nums[i];
            ans += i - cnt[x]++;
        }
        return ans;
    }
};

###go

func countBadPairs(nums []int) (ans int64) {
cnt := map[int]int{}
for i, x := range nums {
x = i - x
ans += int64(i - cnt[x])
cnt[x]++
}
return
}

###ts

function countBadPairs(nums: number[]): number {
    const cnt = new Map<number, number>();
    let ans = 0;
    for (let i = 0; i < nums.length; ++i) {
        const x = i - nums[i];
        ans += i - (cnt.get(x) ?? 0);
        cnt.set(x, (cnt.get(x) ?? 0) + 1);
    }
    return ans;
}

###rust

use std::collections::HashMap;

impl Solution {
    pub fn count_bad_pairs(nums: Vec<i32>) -> i64 {
        let mut cnt: HashMap<i32, i64> = HashMap::new();
        let mut ans: i64 = 0;
        for (i, &num) in nums.iter().enumerate() {
            let x = i as i32 - num;
            let count = *cnt.get(&x).unwrap_or(&0);
            ans += i as i64 - count;
            *cnt.entry(x).or_insert(0) += 1;
        }
        ans
    }
}

时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\textit{nums}$ 的长度。


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

【Java】6142. 统计坏数对的数目 91.71% 附类似等式交换问题

解题思路

j - i != nums[j] - nums[i]
交换一下
j - nums[j] != i - nums[i]
就解了

向前找的问题变成当前 + 哈希问题。避免了向前找的动作。

类似的

[中等] 1814. 统计一个数组中好对子的数目【数组】【哈希表】【数学】【计数】 [哈希表] [1814. 统计一个数组中好对子的数目]

代码

###java

class Solution {
public long countBadPairs(int[] nums) {
long ans = 0;
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int val = i - nums[i];
int same = map.getOrDefault(val, 0);
ans += i - same;
map.put(val, same + 1);
}
return ans;
}
}

每日一题-统计坏数对的数目🟡

给你一个下标从 0 开始的整数数组 nums 。如果 i < j 且 j - i != nums[j] - nums[i] ,那么我们称 (i, j) 是一个 数对 。

请你返回 nums 中 坏数对 的总数目。

 

示例 1:

输入:nums = [4,1,3,3]
输出:5
解释:数对 (0, 1) 是坏数对,因为 1 - 0 != 1 - 4 。
数对 (0, 2) 是坏数对,因为 2 - 0 != 3 - 4, 2 != -1 。
数对 (0, 3) 是坏数对,因为 3 - 0 != 3 - 4, 3 != -1 。
数对 (1, 2) 是坏数对,因为 2 - 1 != 3 - 1, 1 != 2 。
数对 (2, 3) 是坏数对,因为 3 - 2 != 3 - 3, 1 != 0 。
总共有 5 个坏数对,所以我们返回 5 。

示例 2:

输入:nums = [1,2,3,4,5]
输出:0
解释:没有坏数对。

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109

式子变形 + 枚举右维护左(Python/Java/C++/Go/JS/Rust)

前置题目1512. 好数对的数目

题目要求

$$
j - i \ne \textit{nums}[j] - \textit{nums}[i]
$$

移项得

$$
\textit{nums}[i]-i \ne \textit{nums}[j]-j
$$

正难则反,用总数对个数 $\dfrac{n(n-1)}{2}$ 减去满足

$$
\textit{nums}[i]-i = \textit{nums}[j]-j
$$

的数对个数,即为答案。

设 $a[i] = \textit{nums}[i]-i$,就和 1512 题完全一样了。

为什么要先更新 $\textit{ans}$,再更新 $\textit{cnt}$?理由见 1512 题 我的题解

class Solution:
    def countBadPairs(self, nums: List[int]) -> int:
        ans = comb(len(nums), 2)
        cnt = defaultdict(int)
        for i, x in enumerate(nums):
            ans -= cnt[x - i]
            cnt[x - i] += 1
        return ans
class Solution {
    public long countBadPairs(int[] nums) {
        int n = nums.length;
        long ans = (long) n * (n - 1) / 2;
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int i = 0; i < n; i++) {
            int x = nums[i] - i;
            int c = cnt.getOrDefault(x, 0);
            ans -= c;
            cnt.put(x, c + 1);
        }
        return ans;
    }
}
class Solution {
    public long countBadPairs(int[] nums) {
        int n = nums.length;
        long ans = (long) n * (n - 1) / 2;
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int i = 0; i < n; i++) {
            ans -= cnt.merge(nums[i] - i, 1, Integer::sum) - 1;
        }
        return ans;
    }
}
class Solution {
public:
    long long countBadPairs(vector<int>& nums) {
        int n = nums.size();
        long long ans = 1LL * n * (n - 1) / 2;
        unordered_map<int, int> cnt;
        for (int i = 0; i < n; i++) {
            ans -= cnt[nums[i] - i]++;
        }
        return ans;
    }
};
func countBadPairs(nums []int) int64 {
    n := len(nums)
    ans := n * (n - 1) / 2
    cnt := map[int]int{}
    for i, x := range nums {
        ans -= cnt[x-i]
        cnt[x-i]++
    }
    return int64(ans)
}
var countBadPairs = function(nums) {
    const n = nums.length;
    let ans = n * (n - 1) / 2;
    const cnt = new Map();
    for (let i = 0; i < n; i++) {
        const x = nums[i] - i;
        const c = cnt.get(x) ?? 0;
        ans -= c;
        cnt.set(x, c + 1);
    }
    return ans;
};
use std::collections::HashMap;

impl Solution {
    pub fn count_bad_pairs(nums: Vec<i32>) -> i64 {
        let n = nums.len() as i64;
        let mut ans = n * (n - 1) / 2;
        let mut cnt = HashMap::new();
        for (i, x) in nums.into_iter().enumerate() {
            let e = cnt.entry(x - i as i32).or_insert(0);
            ans -= *e as i64;
            *e += 1;
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

Java36毫秒 计数+求和

解题思路

1.差值(数值-下标)相同的数可以组成好数对,按相同差值聚合计数。
2.设数组长度为n,数组总共数对个数为(n-1)+(n-2)+...+1累加之和,形成等差数列,公式求和(1+n-1)*(n-1)/2,得到总数对个数。相同差值的个数也可以视为数组长度,同理得到好数对个数。
3.坏数对=总数对-好数对。

统计坏数对的数目.png

代码

###java

class Solution {
    public long countBadPairs(int[] nums) {
        int n=nums.length;
        long cnt=(long)(1.0d*(1+n-1)*(n-1)/2);
        Map<Integer,Integer> map=new HashMap<>();
        for(int i=0;i<n;i++){
            int d=nums[i]-i;
            map.put(d,map.getOrDefault(d,0)+1);
        }
        for(int count:map.values()){
            cnt-=(long)(1.0d*(1+count-1)*(count-1)/2);
        }
        return cnt;
    }
}

枚举

解法:枚举

坏数对的数目,就是所有数对的数目,减去满足 i < jj - i == nums[j] - nums[i] 的数对数目。

把等式两边移项,变为 nums[i] - i == nums[j] - j。因此我们只需要维护一个 unordered_map<int, int> mpmp[x] 表示目前为止满足 nums[i] - i == xi 有几个。我们枚举 j,并从答案中减去 mp[nums[j] - j] 的值即可。复杂度 $\mathcal{O}(n)$。

参考代码(c++)

###c++

class Solution {
public:
    long long countBadPairs(vector<int>& nums) {
        int n = nums.size();
        long long ans = 1LL * n * (n - 1) / 2;
        unordered_map<int, int> mp;
        for (int i = 0; i < n; i++) {
            int t = nums[i] - i;
            ans -= mp[t];
            mp[t]++;
        }
        return ans;
    }
};

[Python3/Java/C++/Go/TypeScript] 一题一解:枚举(清晰题解)

方法一:枚举

我们先在 $[0, n)$ 的范围内枚举下标 $j$,然后在 $[0, j)$ 的范围内枚举下标 $i$,统计满足 $\textit{nums}[i] = \textit{nums}[j]$ 且 $(i \times j) \bmod k = 0$ 的数对个数。

###python

class Solution:
    def countPairs(self, nums: List[int], k: int) -> int:
        ans = 0
        for j, y in enumerate(nums):
            for i, x in enumerate(nums[:j]):
                ans += int(x == y and i * j % k == 0)
        return ans

###java

class Solution {
    public int countPairs(int[] nums, int k) {
        int ans = 0;
        for (int j = 1; j < nums.length; ++j) {
            for (int i = 0; i < j; ++i) {
                ans += nums[i] == nums[j] && (i * j % k) == 0 ? 1 : 0;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int countPairs(vector<int>& nums, int k) {
        int ans = 0;
        for (int j = 1; j < nums.size(); ++j) {
            for (int i = 0; i < j; ++i) {
                ans += nums[i] == nums[j] && (i * j % k) == 0;
            }
        }
        return ans;
    }
};

###go

func countPairs(nums []int, k int) (ans int) {
for j, y := range nums {
for i, x := range nums[:j] {
if x == y && (i*j%k) == 0 {
ans++
}
}
}
return
}

###ts

function countPairs(nums: number[], k: number): number {
    let ans = 0;
    for (let j = 1; j < nums.length; ++j) {
        for (let i = 0; i < j; ++i) {
            if (nums[i] === nums[j] && (i * j) % k === 0) {
                ++ans;
            }
        }
    }
    return ans;
}

###rust

impl Solution {
    pub fn count_pairs(nums: Vec<i32>, k: i32) -> i32 {
        let mut ans = 0;
        for j in 1..nums.len() {
            for (i, &x) in nums[..j].iter().enumerate() {
                if x == nums[j] && (i * j) as i32 % k == 0 {
                    ans += 1;
                }
            }
        }
        ans
    }
}

###c

int countPairs(int* nums, int numsSize, int k) {
    int ans = 0;
    for (int j = 1; j < numsSize; ++j) {
        for (int i = 0; i < j; ++i) {
            ans += (nums[i] == nums[j] && (i * j % k) == 0);
        }
    }
    return ans;
}

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


有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~

每日一题-统计数组中相等且可以被整除的数对🟢

给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 k ,请你返回满足 0 <= i < j < n ,nums[i] == nums[j] 且 (i * j) 能被 k 整除的数对 (i, j) 的 数目 。

 

示例 1:

输入:nums = [3,1,2,2,2,1,3], k = 2
输出:4
解释:
总共有 4 对数符合所有要求:
- nums[0] == nums[6] 且 0 * 6 == 0 ,能被 2 整除。
- nums[2] == nums[3] 且 2 * 3 == 6 ,能被 2 整除。
- nums[2] == nums[4] 且 2 * 4 == 8 ,能被 2 整除。
- nums[3] == nums[4] 且 3 * 4 == 12 ,能被 2 整除。

示例 2:

输入:nums = [1,2,3,4], k = 1
输出:0
解释:由于数组中没有重复数值,所以没有数对 (i,j) 符合所有要求。

 

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i], k <= 100

双周赛t1 基于筛法的O(nlogn)解法

解题思路

类似于周赛t4 6015. 统计可以被 K 整除的下标对数目

  1. 筛法预处理每个因子的倍数有哪些,记录倍数时也要记录他对应的数组里的值
  2. 遍历每个数,分index=0与index>0的情况
  3. index=0直接特殊处理即可
  4. index>0时加上符合题意的配对数multiCounter[need][value],注意要减去自身重复取的情况,最后结果除以二即可

代码

###python3

from collections import defaultdict
from math import gcd
from typing import Counter, List

class Solution:
    def countPairs(self, nums: List[int], k: int) -> int:
        n = len(nums)
        multiCounter = defaultdict(lambda: defaultdict(int))
        for factor in range(1, n):
            for multi in range(factor, n, factor):
                multiCounter[factor][nums[multi]] += 1

        counter = Counter(nums)
        res1, res2 = 0, 0
        for index, value in enumerate(nums):
            if index == 0:
                res1 += counter[value] - 1
            else:
                need = k // gcd(index, k)
                res2 += multiCounter[need][value]
                if index ** 2 % k == 0:
                    res2 -= 1

        return res1 + res2 // 2

O(nlogn) 数学做法,枚举右维护左(Python/Java/C++/Go)

如果数组长度 $n=10^5$,$\mathcal{O}(n^2)$ 的暴力枚举就超时了,怎么优化?

从特殊到一般,先考虑所有 $\textit{nums}[i]$ 都相同的情况。此时问题简化成:

  • 统计 $0 \le i < j < n$ 且 $ij$ 能被 $k$ 整除的下标对 $(i, j)$ 的数目。

由于 $0$ 不是因子,单独统计($i=0$ 时,$ij=0$,一定能被 $k$ 整除),所以下面讨论 $1 \le i < j < n$ 的情况。

如果 $k=12$,$j=9$,哪些 $i$ 是满足要求的?

$i$ 如果是 $12$ 的倍数,肯定可以满足要求,因为 $12$ 的倍数一定能被 $12$ 整除。还有其他的 $i$ 吗?

注意到 $j=9$ 是 $3$ 的倍数,而 $12=4\times 3$,那么 $i$ 只需要是 $4$ 的倍数,便可以满足 $ij$ 是 $k$ 的倍数,因为这种情况下 $ij=(4i')(3j')=12i'j'$ 一定是 $12$ 的倍数。

换句话说,由于 $j$ 和 $k$ 的最大公因子(GCD)是 $3$,所以 $i$ 只需要是 $\dfrac{12}{3}=4$ 的倍数就可以满足要求,即 $i=4,8,12,\cdots$ 都是满足要求的。

一般地,枚举 $j$,那么 $i$ 必须是 $k'=\dfrac{k}{\text{GCD}(k,j)}$ 的倍数。

回到本题,多了一个元素值必须相同的约束。

遍历数组,设当前元素 $x=\textit{nums}[j]$,我们需要知道左边值为 $x$ 且下标是 $k'$ 的倍数的数的个数。怎么维护?例如 $\textit{nums}[4]=\textit{nums}[6]=50$,对于 $i=4$,把 $50$ 以及 $4$ 的所有因子 $1,2,4$,组成三个二元组 $(50,1),(50,2),(50,4)$ 加到哈希表中(统计二元组的个数);对于 $i=6$,把 $50$ 以及 $6$ 的所有因子 $1,2,3,6$,组成四个二元组 $(50,1),(50,2),(50,3),(50,6)$ 加到哈希表中。这样如果后面遍历到一个值为 $50$ 的数,且 $k'=2$,通过查询哈希表中的 $(50,2)$ 的个数,就能知道,左边有两个数,值为 $50$ 且下标是 $2$ 的倍数。

# 预处理每个数的因子
MX = 101
divisors = [[] for _ in range(MX)]
for i in range(1, MX):
    for j in range(i, MX, i):
        divisors[j].append(i)

class Solution:
    def countPairs(self, nums: list[int], k: int) -> int:
        ans = 0
        cnt = defaultdict(int)
        for j, x in enumerate(nums):  # 枚举 j,计算左边有多少个符合要求的 i
            if j and x == nums[0]:
                ans += 1  # 单独统计 i=0 的情况
            k2 = k // gcd(k, j)  # i 必须是 k2 的倍数
            ans += cnt[(x, k2)]
            for d in divisors[j]:  # j 是 d 的倍数
                cnt[(x, d)] += 1
        return ans
class Solution {
    private static final int MX = 101;
    private static final List<Integer>[] divisors = new ArrayList[MX];

    static {
        Arrays.setAll(divisors, i -> new ArrayList<>());
        // 预处理每个数的因子
        for (int i = 1; i < MX; i++) {
            for (int j = i; j < MX; j += i) {
                divisors[j].add(i);
            }
        }
    }

    public int countPairs(int[] nums, int k) {
        int ans = 0;
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int j = 0; j < nums.length; j++) { // 枚举 j,计算左边有多少个符合要求的 i
            int x = nums[j];
            if (j > 0 && x == nums[0]) {
                ans++; // 单独统计 i=0 的情况
            }
            int k2 = k / gcd(k, j); // i 必须是 k2 的倍数
            // 用位运算把二元组 (x, k2) 合并成一个整数
            ans += cnt.getOrDefault(k2 << 10 | x, 0);
            for (int d : divisors[j]) { // j 是 d 的倍数
                cnt.merge(d << 10 | x, 1, Integer::sum); // cnt[d<<10|x]++
            }
        }
        return ans;
    }

    private int gcd(int a, int b) {
        while (a != 0) {
            int tmp = b % a;
            b = a;
            a = tmp;
        }
        return b;
    }
}
const int MX = 101;
vector<int> divisors[MX];

auto init = [] {
    for (int i = 1; i < MX; i++) {
        for (int j = i; j < MX; j += i) {
            divisors[j].push_back(i);
        }
    }
    return 0;
}();

class Solution {
public:
    int countPairs(vector<int>& nums, int k) {
        int ans = 0;
        unordered_map<int, int> cnt;
        for (int j = 0; j < nums.size(); j++) { // 枚举 j,计算左边有多少个符合要求的 i
            int x = nums[j];
            if (j && x == nums[0]) {
                ans++; // 单独统计 i=0 的情况
            }
            int k2 = k / gcd(k, j); // i 必须是 k2 的倍数
            ans += cnt[k2 << 10 | x]; // 用位运算把二元组 (x, k2) 合并成一个整数
            for (int d : divisors[j]) { // j 是 d 的倍数
                cnt[d << 10 | x]++;
            }
        }
        return ans;
    }
};
const mx = 101
var divisors [mx][]int

func init() {
    // 预处理每个数的因子
    for i := 1; i < mx; i++ {
        for j := i; j < mx; j += i {
            divisors[j] = append(divisors[j], i)
        }
    }
}

func countPairs(nums []int, k int) (ans int) {
    type pair struct{ v, d int }
    cnt := map[pair]int{}
    for j, x := range nums { // 枚举 j,计算左边有多少个符合要求的 i
        if j > 0 && x == nums[0] {
            ans++ // 单独统计 i=0 的情况
        }
        k2 := k / gcd(k, j)
        ans += cnt[pair{x, k2}] // 统计左边有多少个数,值为 x 且下标是 k2 的倍数
        for _, d := range divisors[j] { // j 是 d 的倍数
            cnt[pair{x, d}]++
        }
    }
    return
}

func gcd(a, b int) int { for a != 0 { a, b = b%a, a }; return b }

复杂度分析

调和级数可得,预处理的时间是 $\mathcal{O}(M\log M)$,其中 $M=100$。

  • 时间复杂度:$\mathcal{O}(n\log n)$,其中 $n$ 是 $\textit{nums}$ 的长度。每次计算 GCD 需要 $\mathcal{O}(\log \min(k,n))$ 的时间。遍历 $1$ 到 $n-1$ 的所有因子跟预处理是一样的,由调和级数可得,需要 $\mathcal{O}(n\log n)$ 的时间。
  • 空间复杂度:$\mathcal{O}(n\log n)$。

相似题目

2183. 统计可以被 K 整除的下标对数目

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-统计好子数组的数目🟡

给你一个整数数组 nums 和一个整数 k ,请你返回 nums 中  子数组的数目。

一个子数组 arr 如果有 至少 k 对下标 (i, j) 满足 i < j 且 arr[i] == arr[j] ,那么称它是一个  子数组。

子数组 是原数组中一段连续 非空 的元素序列。

 

示例 1:

输入:nums = [1,1,1,1,1], k = 10
输出:1
解释:唯一的好子数组是这个数组本身。

示例 2:

输入:nums = [3,1,4,3,2,2,4], k = 2
输出:4
解释:总共有 4 个不同的好子数组:
- [3,1,4,3,2,2] 有 2 对。
- [3,1,4,3,2,2,4] 有 3 对。
- [1,4,3,2,2,4] 有 2 对。
- [4,3,2,2,4] 有 2 对。

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i], k <= 109

【普通人包看懂的简单代码】滑动窗口

思路

  1. 下标[left,right]区间内如果满足条件了,那么总长度n-right个都满足条件
  2. 此时left++,以新的起点试图重新判断是否满足条件 再重复步骤1.
class Solution {
public:
    long long countGood(vector<int>& nums, int k) {
        long long res=0;
        int sum=0;//成对的总数
        unordered_map<int,int>cnt;
         for(int left=0,right=0;right<nums.size();right++){
             cnt[nums[right]]++;
            if(cnt[nums[right]]>=2){//可以成对就能为对的总数做贡献
              sum+=cnt[nums[right]]-1;//两个数多一对;三个数多两对;四个数多三对 
            }
            if(sum>=k){//对数不少于k
                while(sum>=k){
                res+=nums.size()-right;//right及其往后的都是好数组
                if(cnt[nums[left]]>=2){//滑出left时 如果left曾经对sum有过贡献 就得把它的贡献减掉
                    sum-=cnt[nums[left]]-1;
                }
                cnt[nums[left]]--;
                left++;//滑出left
                }
            }
         }
         return res;
    }
};

c++滑动窗口

  • 思路
    • 直接枚举区间[i,j]会超时,可以使用滑动窗口来实现
    • 如果[i,j]是好子数组,那么[i,j+1],[i,j+2]...都是好子数组
    • 因此我们只需要枚举左边界i,找到最小的j使得[i,j]是好子数组,也就得到了nums.size()-j个以i开始的好子数组
    • 计算区间内相等元素对的数目:
      • 如果一个数字有k个(k>1)那么相等对有k*(k-1)/2个
      • 因此k加一时数对数目增加k*(k-1)/2-(k-1)*(k-2)/2=k-1个
      • 减少时同理
class Solution {
public:
    long long countGood(vector<int>& nums, int k) {
        long long ans=0;
        unordered_map<int,int>check;//维护滑动窗口内数据数目
        int sum=0;
        for(int i=0,j=0;j<nums.size();j++)
        {
            check[nums[j]]++;
            if(check[nums[j]]>1)//更新数对数目
                sum+=check[nums[j]]-1;
            if(sum>=k)
            {
                while(sum>=k)
                {
                    ans+=nums.size()-j;//更新i开始的好子数组数目
                    if(check[nums[i]]>1)
                        sum-=check[nums[i]]-1;
                    check[nums[i]]--;
                    i++;
                }
            }
        }
        return ans;
    }
};

「越长越合法」型滑动窗口(Python/Java/C++/Go/JS/Rust)

前置知识滑动窗口【基础算法精讲 03】

核心思路

  1. 如果窗口中有 $c$ 个元素 $x$,再进来一个 $x$,会新增 $c$ 个相等数对。
  2. 如果窗口中有 $c$ 个元素 $x$,再去掉一个 $x$,会减少 $c-1$ 个相等数对。

用一个哈希表 $\textit{cnt}$ 维护子数组(窗口)中的每个元素的出现次数,以及相同数对的个数 $\textit{pairs}$。

外层循环:从小到大枚举子数组右端点 $\textit{right}$。现在准备把 $x=\textit{nums}[\textit{right}]$ 移入窗口,那么窗口中有 $\textit{cnt}[x]$ 个数和 $x$ 相同,所以 $\textit{pairs}$ 会增加 $\textit{cnt}[x]$。然后把 $\textit{cnt}[x]$ 加一。

内层循环:如果发现 $\textit{pairs}\ge k$,说明子数组符合要求,右移左端点 $\textit{left}$,先把 $\textit{cnt}[\textit{nums}[\textit{left}]]$ 减少一,然后把 $\textit{pairs}$ 减少 $\textit{cnt}[\textit{nums}[\textit{left}]]$。

内层循环结束后,$[\textit{left},\textit{right}]$ 这个子数组是不满足题目要求的,但在退出循环之前的最后一轮循环,$[\textit{left}-1,\textit{right}]$ 是满足题目要求的。由于子数组越长,越能满足题目要求,所以除了 $[\textit{left}-1,\textit{right}]$,还有 $[\textit{left}-2,\textit{right}],[\textit{left}-3,\textit{right}],\ldots,[0,\textit{right}]$ 都是满足要求的。也就是说,当右端点固定在 $\textit{right}$ 时,左端点在 $0,1,2,\ldots,\textit{left}-1$ 的所有子数组都是满足要求的,这一共有 $\textit{left}$ 个。

class Solution:
    def countGood(self, nums: List[int], k: int) -> int:
        cnt = defaultdict(int)  # 比 Counter() 快
        ans = left = pairs = 0
        for x in nums:
            pairs += cnt[x]
            cnt[x] += 1
            while pairs >= k:
                cnt[nums[left]] -= 1
                pairs -= cnt[nums[left]]
                left += 1
            ans += left
        return ans
class Solution {
    public long countGood(int[] nums, int k) {
        long ans = 0;
        Map<Integer, Integer> cnt = new HashMap<>();
        int pairs = 0;
        int left = 0;
        for (int x : nums) {
            int c = cnt.getOrDefault(x, 0);
            pairs += c; // 进
            cnt.put(x, c + 1);
            while (pairs >= k) {
                x = nums[left];
                c = cnt.get(x);
                pairs -= c - 1; // 出
                cnt.put(x, c - 1);
                left++;
            }
            ans += left;
        }
        return ans;
    }
}
class Solution {
    public long countGood(int[] nums, int k) {
        long ans = 0;
        Map<Integer, Integer> cnt = new HashMap<>();
        int pairs = 0;
        int left = 0;
        for (int x : nums) {
            pairs += cnt.merge(x, 1, Integer::sum) - 1; // pairs += cnt[x]++
            while (pairs >= k) {
                pairs -= cnt.merge(nums[left], -1, Integer::sum); // pairs -= --cnt[nums[left]]
                left++;
            }
            ans += left;
        }
        return ans;
    }
}
class Solution {
public:
    long long countGood(vector<int>& nums, int k) {
        long long ans = 0;
        unordered_map<int, int> cnt;
        int pairs = 0, left = 0;
        for (int x : nums) {
            pairs += cnt[x]++;
            while (pairs >= k) {
                pairs -= --cnt[nums[left]];
                left++;
            }
            ans += left;
        }
        return ans;
    }
};
func countGood(nums []int, k int) (ans int64) {
    cnt := map[int]int{}
    pairs, left := 0, 0
    for _, x := range nums {
        pairs += cnt[x]
        cnt[x]++
        for pairs >= k {
            cnt[nums[left]]--
            pairs -= cnt[nums[left]]
            left++
        }
        ans += int64(left)
    }
    return
}
var countGood = function(nums, k) {
    const cnt = new Map();
    let ans = 0, pairs = 0, left = 0;
    for (const x of nums) {
        const c = cnt.get(x) ?? 0;
        pairs += c; // 进
        cnt.set(x, c + 1);
        while (pairs >= k) {
            const x = nums[left];
            const c = cnt.get(x);
            pairs -= c - 1; // 出
            cnt.set(x, c - 1);
            left++;
        }
        ans += left;
    }
    return ans;
};
use std::collections::HashMap;

impl Solution {
    pub fn count_good(nums: Vec<i32>, k: i32) -> i64 {
        let mut ans = 0;
        let mut cnt = HashMap::new();
        let mut pairs = 0;
        let mut left = 0;
        for &x in &nums {
            let e = cnt.entry(x).or_insert(0);
            pairs += *e;
            *e += 1;
            while pairs >= k {
                let e = cnt.get_mut(&nums[left]).unwrap();
                *e -= 1;
                pairs -= *e;
                left += 1;
            }
            ans += left;
        }
        ans as _
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 为 $\textit{nums}$ 的长度。虽然写了个二重循环,但是内层循环中对 $\textit{left}$ 加一的执行次数不会超过 $n$ 次,所以总的时间复杂度为 $\mathcal{O}(n)$。
  • 空间复杂度:$\mathcal{O}(n)$。

更多相似题目,见下面滑动窗口题单中的「§2.3.1 越长越合法」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-统计数组中好三元组数目🔴

给你两个下标从 0 开始且长度为 n 的整数数组 nums1 和 nums2 ,两者都是 [0, 1, ..., n - 1] 的 排列 。

好三元组 指的是 3 个 互不相同 的值,且它们在数组 nums1 和 nums2 中出现顺序保持一致。换句话说,如果我们将 pos1v 记为值 v 在 nums1 中出现的位置,pos2v 为值 v 在 nums2 中的位置,那么一个好三元组定义为 0 <= x, y, z <= n - 1 ,且 pos1x < pos1y < pos1z 和 pos2x < pos2y < pos2z 都成立的 (x, y, z) 。

请你返回好三元组的 总数目 。

 

示例 1:

输入:nums1 = [2,0,1,3], nums2 = [0,1,2,3]
输出:1
解释:
总共有 4 个三元组 (x,y,z) 满足 pos1x < pos1y < pos1,分别是 (2,0,1) ,(2,0,3) ,(2,1,3) 和 (0,1,3) 。
这些三元组中,只有 (0,1,3) 满足 pos2x < pos2y < pos2z 。所以只有 1 个好三元组。

示例 2:

输入:nums1 = [4,0,1,3,2], nums2 = [4,1,0,2,3]
输出:4
解释:总共有 4 个好三元组 (4,0,3) ,(4,0,2) ,(4,1,3) 和 (4,1,2) 。

 

提示:

  • n == nums1.length == nums2.length
  • 3 <= n <= 105
  • 0 <= nums1[i], nums2[i] <= n - 1
  • nums1 和 nums2 是 [0, 1, ..., n - 1] 的排列。

树状数组/线段树/平衡树

方法一:树状数组/线段树/平衡树

首先用哈希表记录每个数在数组二中的位置,然后按照数组一的顺序依次处理。

我们考虑以当前数字作为三元组中间数字的好三元组的数目。第一个数字需要是之前已经遍历过的,并且在数组二中的位置比当前数字更靠前的;第三个数字需要是当前还没有遍历过的,并且在数组二中的位置比当前数字更靠后的。这里只对数字的位置有要求,而对数字具体的值没有要求。

如何快速求出满足条件的第一个数字和第三个数字的个数呢?

以 $[4,0,1,3,2]\quad[4,1,0,2,3]$为例,考虑我们的遍历过程:

首先处理的是 4,此时数组二中的出现情况为:

$[4,X,X,X,X]$

我们需要统计的是 4 之前的有值的个数(0 个),以及 4 之后的没有值的个数(4 个)。因此以 4 为中间数字能形成 0 个好三元组。

接下来是 0,此时数组二中的出现情况为:

$[4,X,0,X,X]$

0 之前有值的个数(1 个),0 之后没有值的个数(2 个)。因此以 0 为中间数字能形成 2 个好三元组。

接下来是 1,此时数组二中的出现情况为:

$[4,1,0,X,X]$

1 之前有值的个数(1 个),1 之后没有值的个数(2 个)。因此以 1 为中间数字能形成 2 个好三元组。

接下来是 3,此时数组二中的出现情况为:

$[4,1,0,X,3]$

3 之前有值的个数(3 个),3 之后没有值的个数(0 个)。因此以 3 为中间数字能形成 0 个好三元组。

最后是 2,此时数组二中的出现情况为:

$[4,1,0,2,3]$

2 之前有值的个数(3 个),2 之后没有值的个数(0 个)。因此以 2 为中间数字能形成 0 个好三元组。

最后的答案是 4。

因为我们并不关心数字具体的值,而只关心是否出现过,所以我们实际上可以把数组二的出现情况用一个 0–1 数组来表示:

$[1,0,0,0,0]\rightarrow[1,0,1,0,0]\rightarrow[1,1,1,0,0]\rightarrow[1,1,1,0,1]\rightarrow[1,1,1,1,1]$

这时可以看出,我们用树状数组(或者线段树、平衡树)就能快速更新状态,并求出我们需要的两个数值(左边的 1 的个数和右边的 0 的个数)。

理清思路之后,代码的实现是比较容易的。

  • 时间复杂度$\mathcal{O}(N\log N)$。
  • 空间复杂度$\mathcal{O}(N)$。

参考代码(C++)

###cpp

template <class T> class FenwickTree {
  int limit;
  vector<T> arr;

  int lowbit(int x) { return x & (-x); }

public:
  FenwickTree(int limit) {
    this->limit = limit;
    arr = vector<T>(limit + 1);
  }

  void update(int idx, T delta) {
    for (; idx <= limit; idx += lowbit(idx))
      arr[idx] += delta;
  }

  T query(int idx) {
    T ans = 0;
    for (; idx > 0; idx -= lowbit(idx))
      ans += arr[idx];
    return ans;
  }
};

class Solution {
public:
    long long goodTriplets(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        FenwickTree<int> occur(n);
        unordered_map<int, int> pos;
        for (int i = 0; i < n; ++i)
            pos[nums2[i]] = i + 1;
        
        long long ans = 0;
        for (int i = 0; i < n; ++i) {
            int idx = pos[nums1[i]];
            
            int left = occur.query(idx);
            int right = n - idx - (occur.query(n) - occur.query(idx));
            ans += 1LL * left * right;
            
            occur.update(idx, 1);
        }
        
        return ans;
    }
};

转化为经典问题 (求左侧小于当前元素的个数)

解法:转化为经典问题(求左侧小于当前元素的个数)+ 树状数组

设 $i < j < k$,如果 $pos2[nums1[i]] < pos2[nums1[j]] < pos2[nums1[k]]$,那么就找到了一个三元组 $(x,y,z),x = nums1[i], y = nums1[j], z = nums1[k]$。令 $f[i] = pos2[nums1[i]]$,问题由此归结为:求满足 $f[i] < f[j] < f[k] (i < j < k)$ 的三元组 $(i, j, k)$ 的数量(因为 $nums1$ 和 $nums2$ 中不包含重复元素,所以三元组 $i,j,k$ 和三元组 $x,y,z$ 是一一对应的)。

我们可以枚举三元组的中间元素 $j$,令 $left[j] =$ 在 $j$ 的左侧小于 $f[j]$ 的元素数量,$right[j] =$ 在 $j$ 右侧大于 $f[j]$ 的数量,那么以 $j$ 为中间的三元组的数量 $=left[j] \times right[j]$。

这类问题是非常典型的 LC 原题 315. 计算右侧小于当前元素的个数,可以采用 官方题解 的树状数组解法,或者归并的解法。这里我采用的是树状数组解法,代码量小,写起来出错几率小。

class Solution:
    def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:
        n = len(nums1)
        
        pos2 = dict((nums2[i], i) for i in range(n))
        f = [pos2[nums1[i]] for i in range(n)]
        
        def add(x):
            while x <= n:
                t[x] += 1
                x += (x & (-x))
        
        def query(x):
            res = 0
            while x:
                res += t[x]
                x -= (x & (-x))
            return res
        
        left, right = [0] * n, [0] * n
        
        # 计算左侧小于 f[i] 的元素个数
        t = [0] * (n + 1)
        for i in range(n):
            left[i] = query(f[i])
            add(f[i] + 1)
        
        # 计算右侧大于 f[i] 的元素个数
        t = [0] * (n + 1)
        for i in range(n-1, -1, -1):
            right[i] = n - 1 - i - query(f[i] + 1)
            add(f[i] + 1)
            
        res = 0
        for i in range(n):
            res += left[i] * right[i]
            
        return res
class Solution {
public:
    void add(vector<int>& A, int pos, int val) {
        for(; pos < A.size(); pos += (pos & (-pos))) A[pos] += val;
    }
    int query(vector<int>& A, int pos) {
        int res = 0;
        for(; pos; pos -= (pos & (-pos))) res += A[pos];
        return res;
    }
    long long goodTriplets(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        vector<int> pos2(n);
        for(int i = 0; i < nums2.size(); ++i) {
            pos2[nums2[i]] = i;
        }
        
        // 计算左侧小于该元素的个数
        vector<int> tr(n + 1, 0), left(n), right(n);
        for(int i = 0; i < n; ++i) {
            left[i] = query(tr, pos2[nums1[i]]);
            add(tr, pos2[nums1[i]] + 1, 1);
        }
        
        fill(tr.begin(), tr.end(), 0);
        
        // 计算右侧大于该元素的个数
        for(int i = n - 1; i >= 0; --i) {
            right[i] = n - 1 - i - query(tr, pos2[nums1[i]] + 1);
            add(tr, pos2[nums1[i]] + 1, 1);
        }
        
        long long res = 0;
        for(int i = 1; i < n - 1; ++i) {
            res += 1ll * left[i] * right[i];
        }
        
        return res;
    }
};

等价转换 + 树状数组(Java/Python/C++/Go)

提示 1

$\textit{nums}_1$ 要是变成 $[0,1,2,\dots, n-1]$ 就会简单不少。

提示 2

枚举 $y$。


前置知识:置换

置换是一个排列到另一个排列的双射。

以示例 2 为例,定义下列置换 $P(x)$:

$$
\left(\begin{array}{cccc}
x & 0 & 1 & 2 & 3 & 4\
P(x) & 1 & 2 & 4 & 3 & 0
\end{array}\right)
$$

我们可以把 $[4,0,1,3,2]$ 中的每个元素 $x$ 替换为 $P(x)$,这样可以得到一个新的排列 $[0,1,2,3,4]$。同理可以将 $[4,1,0,2,3]$ 通过置换得到新的排列 $[0,2,1,4,3]$。


将 $\textit{nums}_1$ 置换成 $[0,1,2,\dots, n-1]$,设这一置换为 $P(x)$,将 $P(x)$ 也应用到 $\textit{nums}_2$ 上。对于 $\textit{nums}_1$ 和 $\textit{nums}_2$ 中的相同元素,在置换后仍然是相同的,且元素的位置仍然是不变的,因此置换操作不会影响答案个数。

由于 $\textit{nums}_1$ 置换成了 $[0,1,2,\dots, n-1]$,因此置换后的好三元组 $(x,y,z)$ 需满足 $x<y<z$。枚举置换后的 $\textit{nums}_2$ 中的 $y$,问题就变成计算元素 $y$ 的左侧有多少个比 $y$ 小的数,以及右侧有多少个比 $y$ 大的数。这可以用树状数组/线段树/名次树来完成(Python 可以直接用 SortedList),下面代码用的是树状数组。

设 $y$ 的下标为 $i$,且其左侧有 $\textit{less}$ 个数比 $y$ 小,由于比 $y$ 大的数有 $n-1-y$ 个(注意 $y$ 的范围为 $[0,n-1]$),减去左侧比 $y$ 大的 $i-\textit{less}$ 个数,因此 $y$ 右侧有 $n-1-y-(i-\textit{less})$ 个数比它大。所以 $y$ 会有

$$
\textit{less}\cdot(n-1-y-(i-\textit{less}))
$$

个好三元组。

累加所有 $y$ 的好三元组个数,即为答案。

注意下面代码使用的是值域在 $[1,n]$ 的树状数组,需要对插入和查询的数额外加一。

  • 时间复杂度:$O(n\log n)$。
  • 空间复杂度:$O(n)$。
class Solution {
    public long goodTriplets(int[] nums1, int[] nums2) {
        var n = nums1.length;
        var p = new int[n];
        for (var i = 0; i < n; ++i)
            p[nums1[i]] = i;
        var ans = 0L;
        var tree = new int[n + 1];
        for (var i = 1; i < n - 1; ++i) {
            for (var j = p[nums2[i - 1]] + 1; j <= n; j += j & -j) // 将 p[nums2[i-1]]+1 加入树状数组
                ++tree[j];
            var y = p[nums2[i]];
            var less = 0;
            for (var j = y; j > 0; j &= j - 1) // 计算 less
                less += tree[j];
            ans += (long) less * (n - 1 - y - (i - less));
        }
        return ans;
    }
}
class Solution:
    def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:
        n = len(nums1)
        p = [0] * n
        for i, x in enumerate(nums1):
            p[x] = i
        ans = 0
        tree = [0] * (n + 1)
        for i in range(1, n - 1):
            # 将 p[nums2[i - 1]] + 1 加入树状数组
            j = p[nums2[i - 1]] + 1
            while j <= n:
                tree[j] += 1
                j += j & -j
            # 计算 less
            y, less = p[nums2[i]], 0
            j = y
            while j:
                less += tree[j]
                j &= j - 1
            ans += less * (n - 1 - y - (i - less))
        return ans
class Solution {
public:
    long long goodTriplets(vector<int> &nums1, vector<int> &nums2) {
        int n = nums1.size();
        vector<int> p(n);
        for (int i = 0; i < n; ++i)
            p[nums1[i]] = i;
        long long ans = 0;
        vector<int> tree(n + 1);
        for (int i = 1; i < n - 1; ++i) {
            for (int j = p[nums2[i - 1]] + 1; j <= n; j += j & -j) // 将 p[nums2[i-1]]+1 加入树状数组
                ++tree[j];
            int y = p[nums2[i]], less = 0;
            for (int j = y; j; j &= j - 1) // 计算 less
                less += tree[j];
            ans += (long) less * (n - 1 - y - (i - less));
        }
        return ans;
    }
};
func goodTriplets(nums1, nums2 []int) (ans int64) {
n := len(nums1)
p := make([]int, n)
for i, v := range nums1 {
p[v] = i
}
tree := make([]int, n+1)
for i := 1; i < n-1; i++ {
for j := p[nums2[i-1]] + 1; j <= n; j += j & -j { // 将 p[nums2[i-1]]+1 加入树状数组
tree[j]++
}
y, less := p[nums2[i]], 0
for j := y; j > 0; j &= j - 1 { // 计算 less
less += tree[j]
}
ans += int64(less) * int64(n-1-y-(i-less))
}
return
}

附 Python SortedList 做法:

###Python

from sortedcontainers import SortedList

class Solution:
    def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:
        n = len(nums1)
        p = [0] * n
        for i, x in enumerate(nums1):
            p[x] = i
        ans = 0
        s = SortedList()
        for i in range(1, n - 1):
            s.add(p[nums2[i - 1]])
            y = p[nums2[i]]
            less = s.bisect_left(y)
            ans += less * (n - 1 - y - (i - less))
        return ans

有一道题也用到了这种置换思想:

每日一题-统计好三元组🟢

给你一个整数数组 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:

输入: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:

输入: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
❌