Count Good triplets in an Array in Java

In this tutorial, we are going to Count good triplets in an array in Java. Our task is to identify "good triplets" in the two arrays, numA1 and numA2, that are provided. These arrays are permutations of [0, 1,..., n - 1]. A good triplet is a set of three distinct numerical values that appear in numA1 and numA2 in increasing order of the correct position. In order to clarify, a good triplet is defined as a set (x, y, z) such that posn1[x] < posn1[y]< posn1[z] and posn2[x] < posn2[y] < posn2[z], and 0 <= x, y, z <= n - 1. The reason for this is due to the fact that posV1 and posV2 are the index values of v in numA1 and numA2, respectively. The overall number of good triplets can be returned.

Example 1:

Input: numA1 = {3, 0, 2, 1}

`numA2 = {0, 3, 2, 1}`

Output: 2

Explanation: (0, 1, 2), (0, 1, 3), (0, 2, 3), and (1,2,3) are 4 possible combinations of triplets (x, y, z) which are in order of pos1[x] < pos1[y] < pos1[z]. Among these triplets, only (0,1,2) and (0, 2, 3) positions satisfies pos2[x] < pos2[y] < pos2[z] among the triplets. Therefore, the 2 good triplets are returned.

Example 2:

Input: numA1 = {2, 4, 3, 0, 1}

`numA2 = {4, 2, 3, 1, 0}`

Output: 4

Explanation: There are 10 combinations of triplets (x, y, z), which are in order of  pos1[x] < pos1[y] < pos1[z]. The subsequent four combinations (0, 1, 2), (0, 2, 3), (0, 3, 4), and (2, 3, 4) are meet the two prerequisites among the ten combinations.

Approach: Using Divide and Conquer

In this approach, we efficiently count "good triplets" within two arrays named numA1 and numA2. First of all, we initialise the arrays named numA1, numA2, indices, left, and right counts and compute the element indices from Array called numA1 for quick and easy lookup during merging. Now, we will recursively divide the arrays into small sub-arrays and merge them while simultaneously counting the number of good triplets that occured. Let's multiply the corresponding elements of the left and right count arrays and add them to the final result to determine the count of good triplets by iterating across the arrays during the merging step. Finally, we return the count of good triplets.

FileName: GoodTripletsCountInArr.java

`public class GoodTripletsCountInArr {    int[] numA1, numA2, indicies1;    int[] left, right;    public long goodTriplets(int[] numA1, int[] numA2) {        int len = numA1.length;        this.numA1 = numA1;        this.numA2 = numA2;        indicies1 = new int[len];        for (int i = 0; i < len; ++i) {            indicies1[numA1[i]] = i;        }        left = new int[len];        right = new int[len];        divideAndConquer(0, len - 1);        long output = 0L;        for (int i = 0; i < len; ++i) {            output += (long)left[i] * (long)right[i];        }        return output;    }    public void divideAndConquer(int l, int r) {        if (l == r) {            return;        }        // Divide        int mid = l + (r - l) / 2;        divideAndConquer(l, mid);        divideAndConquer(mid + 1, r);        // Conquer        int i = 0, j = 0, k = 0;        int[] temp1 = new int[mid - l + 1];        int[] temp2 = new int[r - mid];        System.arraycopy(numA2, l, temp1, 0, mid - l + 1);        System.arraycopy(numA2, mid + 1, temp2, 0, r - mid);        int[] temp = new int[r - l + 1];        // populating left        while (i < temp1.length && j < temp2.length) {            if (indicies1[temp1[i]] < indicies1[temp2[j]]) {                temp[k] = temp1[i];                ++i;            } else {                temp[k] = temp2[j];                left[temp2[j]] += i;                ++j;            }            ++k;        }        while (i < temp1.length) {            temp[k] = temp1[i];            ++i;            ++k;        }        while (j < temp2.length) {            temp[k] = temp2[j];            left[temp2[j]] += i;            ++j;            ++k;        }        System.arraycopy(temp, 0, numA2, l, r - l + 1);        // Populating right        i = temp1.length - 1;        j = temp2.length - 1;        k = temp.length - 1;        while (i >= 0 && j >= 0) {            if (indicies1[temp1[i]] > indicies1[temp2[j]]) {                right[temp1[i]] += temp2.length - j - 1;                --i;            } else {                --j;            }        }        while (i >= 0) {            right[temp1[i]] += temp2.length - j - 1;            --i;        }    } // main method    public static void main(String[] args) {       // provide input values      int[] numA1 = {4, 0, 1, 3, 2};        int[] numA2 = {4, 1, 0, 2, 3};GoodTripletsCountInArr gt = new GoodTripletsCountInArr ();        long res = gt.goodTriplets(numA1, numA2);        System.out.println("Number of good triplets: " + res);    }}`

Output:

`Number of good triplets: 4`

Complexity Analysis: The program uses a divide and conquer algorithm that gives time complexity of O(n) because of a recursive division of arrays. Merging subarrays and updating left and right arrays also takes O(N) time, so the final result is O(N * log(N)) complexity. The space complexity is O(N) because the algorithm uses n arrays. Also, the recursion stack takes extra space of O(logN) during the divide and conquer process. Hence, the overall space complexity is O(N).

Approach: Using Binary Indexed Tree

In this approach, firstly, we initialize variables that are required, including arrays left and right to monitor the counts for each element on the left and right, and resl to keep the count of good triplets. Also, declare an array called posn to store the positions of elements in the numA2 array. Now, initialise a BIT with the length of numA1, iterate over the numA2 array to fill the posn array with element indices, and then move from left to right through the numA1 array. Using BIT, update the left array. After that, repeat the traversal, compute the index of each element as well as the count of elements remaining on the right, and update the right array with the help of BIT. Iterate over the numA1 array and multiply the corresponding elements from both arrays. Let's store the outcome in the variable called resl. Finally, we should return the count of good triplets.

FileName: FindTheCountOfGoodTriplets.java

`public class FindTheCountOfGoodTriplets {public long goodTriplets(int[] numA1, int[] numA2) {        long resl = 0;     // arrays to store count values        long[] leftCnt = new long[numA1.length];        long[] rightCnt = new long[numA1.length];        int[] posn = new int[numA1.length];        for (int i = 0; i < numA1.length; i++) posn[numA2[i]] = i;// creating a binary indexed tree        BinaryIndexedTree bTree = new BinaryIndexedTree (numA1.length);   // left counts        for (int i = 0; i < numA1.length; i++){            int idx = posn[numA1[i]] + 1;            leftCnt[i] = bTree.queryBIT(idx - 1);            bTree.updateBIT(idx, 1);        }        bTree = new BinaryIndexedTree (numA1.length);// right counts        for (int i = numA1.length - 1; i >= 0; i--){            int idx = posn[numA1[i]] + 1;            int currTotal = numA1.length - 1 - i;            rightCnt[i] = currTotal - bTree.queryBIT(idx);            bTree.updateBIT(idx, 1);        }// counting for good triplets       for (int i = 0; i < numA1.length; i++)            resl += leftCnt[i] * rightCnt[i];        return resl;    }// binary indexed tree classclass BinaryIndexedTree {        private int[] tree;        public BinaryIndexedTree(int n){            tree = new int[n + 2];        }  // find least significant bit        private int lsb(int i){            return i &(-i);        }    // query method        public int queryBIT(int i){            int Totalsum = 0;            for (; i > 0; i -= lsb(i)) Totalsum += tree[i];            return Totalsum;        }   // update method        public void updateBIT(int i, int val){            for (; i < tree.length; i += lsb(i)) tree[i] += val;        }    }// main method    public static void main(String[] args) {        FindTheCountOfGoodTriplets gt = new FindTheCountOfGoodTriplets();// Provide input values int[] numA1 = {3, 0, 2, 1};             int[] numA2 = {0, 3, 2, 1};        long resl = gt.goodTriplets(numA1, numA2);        System.out.println("Count of good triplets: " + resl);    }}`

Output:

`Count of good triplets: 2`

Complexity Analysis: The Program has the time complexity of O(n log n), where n is the length of input arrays numA1 and numA2. Because it uses a Binary Indexed Tree (BIT) for efficient range queries and updates, taking O(n log n) time for the construction and calculation of left and right counts. The space complexity is O(n) due to the use of two additional arrays, leftCnt and rightCnt, of length n, and the BIT data structure requires an array of length n+2.

Approach: Using Segment Tree

The approach follows a systematic process. Firstly, to quickly access an element's index in numA2, construct a mapping named elemToIdxMappingInB. Initialize a segment tree named segTree with a size of n * 4 + 1, where n is the length of numA1. Now, Iterate over numA1 and retrieve the current element index. Let's calculate the number of common elements on the left side, then find the number of unique elements; after that, compute the number of common elements on the right side. Update the result by adding the product of the number of common elements on the left and right sides. Return the final result, which represents the count of good triplets.

FileName: FindTheCountOfGoodTriplets.java

`// importing HashMap and Map interfacesimport java.util.HashMap;import java.util.Map;public class FindTheCountOfGoodTriplets {    public long goodTriplets(int numA1[], int numA2[]) {        Map<Integer, Integer> elemToIdxMappingInB = new HashMap<>();        int n = numA1.length;        long segTree[] = new long[n * 4 + 1];        long resl = 0;        for (int i = 0; i < numA2.length; i++) {            elemToIdxMappingInB.put(numA2[i], i);        }        update(segTree, 1, 0, n - 1, elemToIdxMappingInB.get(numA1[0]));        for (int i = 1; i < n; i++) {            int indexInB = elemToIdxMappingInB.get(numA1[i]);           // left count            long commonEleOnLeft = query(segTree, 1, 0, n - 1, 0, indexInB);            long uniqueEleOnLeftInA = i - commonEleOnLeft;            long eleAfterIndexInB = n - 1 - indexInB;          // right count            long commonEleOnRight = eleAfterIndexInB - uniqueEleOnLeftInA;            resl += commonEleOnLeft * commonEleOnRight;            update(segTree, 1, 0, n - 1, indexInB);        }        return resl;    }   // update method    private void update(long[] st, int idx, int start, int end, int updateIdx) {        if (start == end) {            st[idx] += 1;            return;        }        int mid = start + (end - start) / 2;        if (updateIdx <= mid) update(st, idx * 2, start, mid, updateIdx);        else update(st, idx * 2 + 1, mid + 1, end, updateIdx);        st[idx] = st[idx * 2] + st[idx * 2 + 1];    }            // query method    private long query(long[] st, int idx, int start, int end, int queryStart, int queryEnd) {        if (end < queryStart || start > queryEnd) return 0;        if (start >= queryStart && end <= queryEnd) return st[idx];        int mid = start + (end - start) / 2;        long left = query(st, idx * 2, start, mid, queryStart, queryEnd);        long right = query(st, idx * 2 + 1, mid + 1, end, queryStart, queryEnd);        return left + right;    }            // main method    public static void main(String[] args) {        FindTheCountOfGoodTriplets gt = new FindTheCountOfGoodTriplets();            // provide input values        int[] numA1 = {2, 4, 3, 0, 1};        int[] numA2 = {4, 2, 3, 1, 0};        long resl = gt.goodTriplets(numA1, numA2);        System.out.println("Count of good triplets: " + resl);    }}`

Output:

`Count of good triplets: 4`

Time complexity: The program consumes time complexity of O(n) to loop over numA2, and for loop over numA1 takes O(n log N). Therefore, the overall time complexity is O(n log n + n), i.e., O(n log n).

Space complexity: The space complexity is O(2n) = O(n) because the program takes space for the elemToIdxMappingInB map and segment tree.

Approach: Using Brute Force

In this approach, first of all, we create an array called posn to store positions of elements from array numA2 and iterate through it. Then, store the elements index in it. For each at index I in numA1, find its position mid numA2 by using array posn. Now, initialize two variables named cntLeft and cntRight to count the elements on the left and right sides of numA1. Multiply the variables cntLeft and cntRight to find the count of good triplets. Let's store the result in a variable called resl. Finally, whatever the count of good triplets obtained are returned.

FileName: FindTheCountOfGoodTriplets.java

`public class FindTheCountOfGoodTriplets {     public long goodTriplets(int[] numA1, int[] numA2) {        long resl = 0;        int posn[] = new int[numA1.length];        for (int i = 0; i < numA2.length; i++)            posn[numA2[i]] = i;      for (int i = 1; i < numA1.length - 1; i++) {            int mid = posn[numA1[i]];            int cntLeft = 0, cntRight = 0;            for (int j = 0; j < i; j++) if (posn[numA1[j]] < mid) cntLeft++;            for (int j = i + 1; j < numA1.length; j++) if (posn[numA1[j]] > mid) cntRight++;            resl += cntLeft * cntRight;        }        return resl;    }    // main method    public static void main(String[] args) {        FindTheCountOfGoodTriplets gt = new FindTheCountOfGoodTriplets();        // provide input values        int numA1[] = {2, 4, 3, 0, 1};        int numA2[] = {4, 2, 3, 1, 0};        long resl = gt.goodTriplets(numA1, numA2);        System.out.println("Number of good triplets: " + resl);    }}`

Output:

`Number of good triplets: 4`

Time complexity: The above program uses the Brute force approach, taking the time complexity of O(n^2) for two nested loops to iterate over arrays numA1 and numA2.

Space Complexity: The space complexity is O(n) because an array called posn of the same length as numA1 is used to store the positions of elements in numA2. Therefore, the space complexity is O(n), where n is the length of numA1 and numA2 arrays.