Count Good Triplets in an Array
HARDDescription
You are given two 0-indexed arrays nums1 and nums2 of length n, both of which are permutations of [0, 1, ..., n - 1].
A good triplet is a set of 3 distinct values which are present in increasing order by position both in nums1 and nums2. In other words, if we consider pos1v as the index of the value v in nums1 and pos2v as the index of the value v in nums2, then a good triplet will be a set (x, y, z) where 0 <= x, y, z <= n - 1, such that pos1x < pos1y < pos1z and pos2x < pos2y < pos2z.
Return the total number of good triplets.
Example 1:
Input: nums1 = [2,0,1,3], nums2 = [0,1,2,3] Output: 1 Explanation: There are 4 triplets (x,y,z) such that pos1x < pos1y < pos1z. They are (2,0,1), (2,0,3), (2,1,3), and (0,1,3). Out of those triplets, only the triplet (0,1,3) satisfies pos2x < pos2y < pos2z. Hence, there is only 1 good triplet.
Example 2:
Input: nums1 = [4,0,1,3,2], nums2 = [4,1,0,2,3] Output: 4 Explanation: The 4 good triplets are (4,0,3), (4,0,2), (4,1,3), and (4,1,2).
Constraints:
n == nums1.length == nums2.length3 <= n <= 1050 <= nums1[i], nums2[i] <= n - 1nums1andnums2are permutations of[0, 1, ..., n - 1].
Approaches
Checkout 2 different approaches to solve Count Good Triplets in an Array. Click on different approaches to view the approach and algorithm in detail.
Fenwick Tree (Binary Indexed Tree)
The O(n^2) approach is too slow because counting smaller elements to the left and larger elements to the right takes O(n) time for each middle element. This counting process can be optimized using a data structure that supports fast queries and updates.
A Fenwick Tree (or Binary Indexed Tree, BIT) is ideal for this. It can count the number of elements in a range and add a new element in O(log n) time. By processing the elements of the transformed array A and using a BIT, we can calculate the required counts efficiently.
We can either use two passes over the array A—one forward and one backward, each with its own BIT—to compute arrays of smaller_left and larger_right counts. Or, more elegantly, we can devise a single-pass algorithm that calculates the contribution of each element as it iterates, using just one BIT.
Algorithm
- First, perform the same problem transformation as in the brute-force approach. Create
pos2map and the arrayAwhereA[i] = pos2[nums1[i]]. - The goal is to efficiently count, for each
j, the number of smaller elements to the left (smaller_left) and larger elements to the right (larger_right). - Two-Pass Method:
- Calculate
smaller_leftfor allj: Initialize a Fenwick Tree (BIT). Iteratejfrom0ton-1. For eachj,smaller_left[j]is the count of numbers less thanA[j]seen so far, which can be found withBIT.query(A[j]). Then, update the BIT withA[j]by callingBIT.update(A[j] + 1, 1). - Calculate
larger_rightfor allj: Initialize a new BIT. Iteratejfromn-1down to0. For eachj,larger_right[j]is the count of numbers greater thanA[j]seen so far (i.e., to the right). This is(elements processed so far) - (elements <= A[j]), which can be found withBIT.query(n) - BIT.query(A[j] + 1). Then, update the BIT withA[j]. - Finally, iterate from
j=0ton-1and sum up the products(long)smaller_left[j] * larger_right[j].
- Calculate
- Single-Pass Optimization:
- We can compute the total count in a single pass. Initialize one BIT and a
total_tripletscounter. - Iterate
jfrom0ton-1. - In each iteration,
A[j]is the middle element. The elementsA[0...j-1]are already processed and stored in the BIT. smaller_left = BIT.query(A[j]). This is the count of elements seen so far that are smaller thanA[j].larger_rightcan be calculated without a second pass. The total count of numbers in the universe[0...n-1]that are greater thanA[j]is(n - 1 - A[j]). Some of these are on the left ofj, and some are on the right. The count of numbers greater thanA[j]on the left islarger_left = (j) - smaller_left. So,larger_right = (total_greater) - (larger_left) = (n - 1 - A[j]) - (j - smaller_left).- Add
(long)smaller_left * larger_righttototal_triplets. - Update the BIT with
A[j]for future iterations:BIT.update(A[j] + 1, 1).
- We can compute the total count in a single pass. Initialize one BIT and a
The core idea is to optimize the counting of smaller_left and larger_right for each potential middle element A[j]. After transforming the problem to finding increasing subsequences of length 3 in array A, we can proceed with a single-pass approach using a Fenwick Tree.
We iterate through A from left to right (index j). For each element A[j], we treat it as the middle element of a triplet (A[i], A[j], A[k]) where i < j < k.
-
Count smaller elements on the left (
smaller_left): The elementsA[0], ..., A[j-1]have already been processed. We can use a Fenwick Tree to maintain the counts of these numbers. A query to the BIT,BIT.query(A[j]), will give us the number of elements seen so far that are strictly less thanA[j]. This is oursmaller_leftcount. -
Count larger elements on the right (
larger_right): We need to count elementsA[k]wherek > jandA[k] > A[j]. We can deduce this count without iterating. The total number of values in the permutation greater thanA[j]is(n - 1) - A[j]. These values are distributed to the left and right of indexj. We can find how many are on the left:larger_leftis the total number of elements to the left (j) minus those that are smaller (smaller_left). So,larger_left = j - smaller_left. Therefore,larger_right = (total_values > A[j]) - larger_left = (n - 1 - A[j]) - (j - smaller_left). -
Update total: For the current middle element
A[j], we have foundsmaller_leftandlarger_right. We add their product to our running total. -
Update BIT: After processing
A[j], we must add it to our Fenwick Tree so it's included in the counts for subsequent elements. We do this withBIT.update(A[j] + 1, 1).
This single pass correctly accumulates the total count of good triplets.
class Solution {
// Fenwick Tree (Binary Indexed Tree) implementation
class FenwickTree {
private int[] bit;
private int size;
public FenwickTree(int size) {
this.size = size;
this.bit = new int[size + 1];
}
public void update(int index, int delta) {
while (index <= size) {
bit[index] += delta;
index += index & -index; // Move to the next relevant index
}
}
public int query(int index) {
int sum = 0;
while (index > 0) {
sum += bit[index];
index -= index & -index; // Move to the parent index
}
return sum;
}
}
public long goodTriplets(int[] nums1, int[] nums2) {
int n = nums1.length;
int[] pos2 = new int[n];
for (int i = 0; i < n; ++i) {
pos2[nums2[i]] = i;
}
int[] A = new int[n];
for (int i = 0; i < n; ++i) {
A[i] = pos2[nums1[i]];
}
// The values in A are 0 to n-1. BIT needs size n+1 for 1-based indexing.
FenwickTree bit = new FenwickTree(n);
long totalTriplets = 0;
for (int j = 0; j < n; ++j) {
// A[j] is the middle element. Values are 0-indexed.
// Query for elements < A[j]. BIT is 1-indexed, so query up to A[j].
int smaller_left = bit.query(A[j]);
// Number of elements processed so far (to the left of j) is j.
// Number of elements > A[j] on the left.
int larger_left = j - smaller_left;
// Total numbers in the permutation > A[j] is (n - 1 - A[j]).
// Number of elements > A[j] on the right.
int larger_right = (n - 1 - A[j]) - larger_left;
totalTriplets += (long) smaller_left * larger_right;
// Add current element A[j] to BIT for subsequent calculations.
// Update at index A[j] + 1 because BIT is 1-indexed.
bit.update(A[j] + 1, 1);
}
return totalTriplets;
}
}
Complexity Analysis
Pros and Cons
- Highly efficient with O(n log n) time complexity, which passes the given constraints.
- It's a standard and powerful technique for solving a class of problems related to counting inversions or ordered subsequences.
- Requires knowledge of advanced data structures like Fenwick Trees or Segment Trees.
- Implementation is more complex than the brute-force approach.
Code Solutions
Checking out 3 solutions in different languages for Count Good Triplets in an Array. Click on different languages to view the code.
class Solution {
public
long goodTriplets(int[] nums1, int[] nums2) {
int n = nums1.length;
int[] pos = new int[n];
BinaryIndexedTree tree = new BinaryIndexedTree(n);
for (int i = 0; i < n; ++i) {
pos[nums2[i]] = i + 1;
}
long ans = 0;
for (int num : nums1) {
int p = pos[num];
long left = tree.query(p);
long right = n - p - (tree.query(n) - tree.query(p));
ans += left * right;
tree.update(p, 1);
}
return ans;
}
} class BinaryIndexedTree {
private
int n;
private
int[] c;
public
BinaryIndexedTree(int n) {
this.n = n;
c = new int[n + 1];
}
public
void update(int x, int delta) {
while (x <= n) {
c[x] += delta;
x += lowbit(x);
}
}
public
int query(int x) {
int s = 0;
while (x > 0) {
s += c[x];
x -= lowbit(x);
}
return s;
}
public
static int lowbit(int x) { return x & -x; }
}
Video Solution
Watch the video walkthrough for Count Good Triplets in an Array
Similar Questions
5 related questions you might find useful
Algorithms:
Data Structures:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.