2198. Number of Single Divisor Triplets


Problem Description

You are tasked with finding the number of special triplets in a given array of positive integers, where the array is indexed starting at 0. A triplet consists of three distinct indices (i, j, k). This triplet is considered special - termed as a single divisor triplet - if the sum of the numbers at these indices, specifically nums[i] + nums[j] + nums[k], is divisible by exactly one of the three numbers nums[i], nums[j], or nums[k]. The goal is to count how many such triplets exist in the provided array.

Intuition

To solve this problem, we should first understand that checking every possible triplet would be quite inefficient if we did so naïvely, since that requires looking at all possible combinations of numbers in the array. However, by utilizing the Counter class from Python's collections module, we can improve the efficiency when certain numbers are repeated in nums.

The solution approach involves the following steps:

  1. We count the occurrences of each number in the array utilizing the Counter class.
  2. Once we have the counts of each number, we iterate through the array thrice – simulating the selection of three distinct elements, denoted by a, b, and c, for our triplet. If a, b, and c are the same number, we skip to avoid checking the same combination again. It's important to note that a, b, or c could be the same number but from different indices.
  3. For each triplet, we check if the sum of a, b, and c (s = a + b + c) is divisible by only one of the three numbers. The check function helps in this regard by returning True if exactly one divisibility condition is met.
  4. If the check function returns True, we must account for the occurrences of each number. Since the numbers might be duplicated in nums, we need to calculate the number of triplets that we can form with the given counts of a, b, and c. We do this by considering the different scenarios:
    • If two numbers are the same and the third is different, we use combinations like cnt1 * (cnt1 - 1) * cnt3 to reflect the number of possible triplets.
    • If all three numbers are different, we simply multiply the counts: cnt1 * cnt2 * cnt3.
  5. We sum up these counts to get the total number of single divisor triplets.

Using the Counter allows us to avoid unnecessary duplicate computations and vastly reduces the number of required operations, especially when dealing with an array containing many repeated numbers.

Learn more about Math patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?

Solution Approach

The implementation of the solution leverages the Counter class from Python's collections module to efficiently track the number of times each number appears in the input array nums. This is crucial to avoid duplicate computations when numbers are repeated. Here's a step-by-step breakdown of the algorithm:

  1. A Counter object is created for the array nums to get the count of each unique number. For example, if nums is [1, 2, 2, 3], the Counter object would be {1: 1, 2: 2, 3: 1}.

  2. We define a helper function check(a, b, c) which calculates the sum s of the parameters a, b, and c. It then checks if s is divisible by exactly one of the values a, b, or c by using the modulo operation s % x for each and counts the occurrences of 0 (true divisibility) using a generator expression. If this count is 1, it returns True; otherwise, it returns False.

  3. The solution then iterates through every possible combination of the counts of each unique number, looking for ways to form triplets. The outer loop picks a number, denoted as a, from the Counter. The nested loops pick additional numbers, b and c.

  4. For each triplet (a, b, c), it calls the check function to verify whether it's a single divisor triplet. If it is, the count of possible single divisor triplets that can be made with a, b, and c is calculated. This depends on whether a, b, and c are the same or different. For example:

    • If a is the same as b but different from c, the number of triplets is cnt1 * (cnt1 - 1) / 2 * cnt3 because there are cnt1 * (cnt1 - 1) / 2 ways to choose two numbers a and b from cnt1 occurrences, and cnt3 ways to choose c.
    • If all three numbers are different, the number of triplets will be cnt1 * cnt2 * cnt3 since there are cnt1 ways to choose a, cnt2 ways to choose b, and cnt3 ways to choose c.
  5. It's essential to handle overcounting carefully; the solution ensures that each triplet of numbers is counted exactly once, with attention to the distinction between using combinations vs. permutations when selecting elements from counts.

  6. As each valid triplet is found, its count is added to an accumulator variable, ans, which finally holds the total count of single divisor triplets.

  7. After checking all possible combinations of numbers from the Counter, the final result is returned, which is the accumulated count of all valid triplets.

Thus, the algorithm makes smart use of combinatorics and the properties of divisibility to compute the desired count without exhaustively examining each distinct triplet in the original array.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?

Example Walkthrough

Let's walk through an example using the provided solution approach to get a clearer understanding of how the algorithm works.

Given Array

Consider the array nums = [1, 2, 2, 3, 4].

Step 1: Counter Object Creation

We create a Counter object from nums which gives us {1: 1, 2: 2, 3: 1, 4: 1}.

Step 2: Define the check function

The check(a, b, c) function takes three numbers and returns True if the sum s = a + b + c is divisible by exactly one of a, b, or c.

Step 3: Iterate over unique combinations

We start iterating over every unique combination using the counts from the Counter object:

  • First we pick a = 1 from the counter.
  • Then we pick b = 2. Since we have two 2's, cnt2 = 2.
  • Next, we pick c = 4. Since there is only one 4, cnt3 = 1.

Step 4: Apply the check function and calculate the triplet count

Using the check function, we find that 1 + 2 + 4 = 7 is not divisible by any number exactly once, so we discard this triplet.

Next, we try a = 2, b = 2, and c = 4 (where a and b are the same). The sum is 2 + 2 + 4 = 8, and this sum is divisible by 4 only, which makes it a valid triplet. Since a and b are the same, there are cnt2 * (cnt2 - 1) / 2 ways to pick a and b, and cnt3 ways to pick c. This gives us a count of (2 * (2 - 1) / 2) * 1 = 1.

We continue this process for other possible combinations.

Step 5: Add valid triplets count to the accumulator

Each time we find a valid triplet combination, we increment our accumulator ans with the count of its occurrences.

Step 6: Final result

After iterating through all unique number combinations from the Counter, we sum up all valid triplet counts we have found. Suppose we found another valid triplet using different numbers, let's say we found 1 more valid triplet in the process. Our total count ans would be 1 + 1 = 2.

Thus, for the given array [1, 2, 2, 3, 4], our final result is 2, meaning there are two single divisor triplets whose sum is divisible exactly by one of the three numbers.

Solution Implementation

1from collections import Counter
2from typing import List
3
4class Solution:
5    def singleDivisorTriplet(self, nums: List[int]) -> int:
6        # A helper function to check if only one of a, b, or c is a divisor of their sum
7        def is_single_divisor_triplet(a: int, b: int, c: int) -> bool:
8            sum_triplet = a + b + c
9            return sum(sum_triplet % x == 0 for x in [a, b, c]) == 1
10
11        # Count the occurrences of each number in the list 
12        num_counter = Counter(nums)
13        count_triplets = 0  # Initialize the count of valid triplets
14
15        # Iterate over all possible combinations of a, b, and c
16        for a, count_a in num_counter.items():
17            for b, count_b in num_counter.items():
18                for c, count_c in num_counter.items():
19                    # Check if the current combination is a single divisor triplet
20                    if is_single_divisor_triplet(a, b, c):
21                        # If any two numbers are equal, adjust the count accordingly
22                        if a == b and b == c:
23                            # All numbers are the same
24                            count_triplets += count_a * (count_a - 1) * (count_a - 2) // 6
25                        elif a == b:
26                            count_triplets += count_a * (count_a - 1) // 2 * count_c
27                        elif a == c:
28                            count_triplets += count_a * (count_a - 1) // 2 * count_b
29                        elif b == c:
30                            count_triplets += count_a * count_b * (count_b - 1) // 2
31                        else:
32                            # All numbers are different
33                            count_triplets += count_a * count_b * count_c
34
35        # Since each triplet is counted 6 times (all permutations), we divide by 6
36        return count_triplets // 6
37
38# The above code can be used as follows:
39# solution = Solution()
40# result = solution.singleDivisorTriplet([nums])
41
1class Solution {
2    public long singleDivisorTriplet(int[] nums) {
3        // Initialize a counter array to count the occurrences of each number up to 100.
4        int[] count = new int[101];
5        for (int num : nums) {
6            count[num]++;
7        }
8
9        // Initialize a variable to store the answer.
10        long answer = 0;
11
12        // Iterate through all possible trios and calculate the sum.
13        for (int i = 1; i <= 100; i++) {
14            for (int j = 1; j <= 100; j++) {
15                for (int k = 1; k <= 100; k++) {
16                    // Get the count of each number in the trio.
17                    int countI = count[i];
18                    int countJ = count[j];
19                    int countK = count[k];
20
21                    int sum = i + j + k;
22
23                    // Check if exactly one number divides the sum.
24                    int divisorCount = 0;
25                    divisorCount += sum % i == 0 ? 1 : 0;
26                    divisorCount += sum % j == 0 ? 1 : 0;
27                    divisorCount += sum % k == 0 ? 1 : 0;
28
29                    // If not exactly one divisor, then continue to the next trio.
30                    if (divisorCount != 1) {
31                        continue;
32                    }
33
34                    // Calculate the number of valid configurations.
35                    if (i == j && i == k) {
36                        // All three numbers are the same.
37                        answer += (long) countI * (countI - 1) * (countI - 2) / 6;
38                    } else if (i == j) {
39                        // i and j are the same, but k is different.
40                        answer += (long) countI * (countI - 1) / 2 * countK;
41                    } else if (i == k) {
42                        // i and k are the same, but j is different.
43                        answer += (long) countI * (countI - 1) / 2 * countJ;
44                    } else if (j == k) {
45                        // j and k are the same, but i is different.
46                        answer += (long) countI * countJ * (countJ - 1) / 2;
47                    } else {
48                        // All three numbers are different.
49                        answer += (long) countI * countJ * countK;
50                    }
51                }
52            }
53        }
54
55        // Return the computed answer.
56        return answer;
57    }
58}
59
1#include <vector>
2using namespace std;
3
4class Solution {
5public:
6    long long singleDivisorTriplet(vector<int>& nums) {
7        // Counter array to keep the frequency of each number from 1 to 100
8        vector<int> frequency(101, 0);
9        // Fill the counter with frequencies of numbers in the input vector nums
10        for (int number : nums) {
11            frequency[number]++;
12        }
13      
14        long long answer = 0; // Variable to store the final count of triplets
15      
16        // Check all possible combinations of three numbers (i, j, k)
17        for (int i = 1; i <= 100; ++i) {
18            for (int j = 1; j <= 100; ++j) {
19                for (int k = 1; k <= 100; ++k) {
20                    // Fetch the frequencies of the current combination
21                    int freq_i = frequency[i];
22                    int freq_j = frequency[j];
23                    int freq_k = frequency[k];
24                  
25                    // Calculate the sum of the triplet
26                    int sum = i + j + k;
27                    // Count the number of divisors the sum has from the triplet
28                    int divisorCount = (sum % i == 0) + (sum % j == 0) + (sum % k == 0);
29                  
30                    // We are only interested in triplets where exactly one of the numbers divides the sum
31                    if (divisorCount != 1) continue;
32                  
33                    // Now, handle the cases depending on the uniqueness of i, j, k
34                    if (i == j && i == k) {
35                        // All numbers are the same
36                        answer += freq_i * (freq_i - 1) * (freq_i - 2) / 6; // Combination of nC3
37                    } else if (i == j) {
38                        // i and j are the same, k is different
39                        answer += (long long) freq_i * (freq_i - 1) / 2 * freq_k; // Combination of nC2 times the count of k
40                    } else if (i == k) {
41                        // i and k are the same, j is different
42                        answer += (long long) freq_i * (freq_i - 1) / 2 * freq_j; // Combination of nC2 times the count of j
43                    } else if (j == k) {
44                        // j and k are the same, i is different
45                        answer += (long long) freq_j * (freq_j - 1) / 2 * freq_i; // Combination of nC2 times the count of i
46                    } else {
47                        // i, j, and k are all different
48                        answer += (long long) freq_i * freq_j * freq_k; // Just the product of the counts
49                    }
50                }
51            }
52        }
53      
54        // Return the total count of single divisor triplets
55        return answer;
56    }
57};
58
1const MAX_NUM = 100;
2
3// Function to count single divisor triplets
4function singleDivisorTriplet(nums: number[]): number {
5    // Initialize a counter array to keep the frequency of each number from 1 to 100
6    const frequency: number[] = new Array(MAX_NUM + 1).fill(0);
7  
8    // Fill the counter with frequencies of numbers in the input array nums
9    nums.forEach(number => {
10        frequency[number]++;
11    });
12
13    let answer: number = 0; // Variable to store the final count of triplets
14
15    // Check all possible combinations of three numbers (i, j, k)
16    for (let i = 1; i <= MAX_NUM; ++i) {
17        for (let j = 1; j <= MAX_NUM; ++j) {
18            for (let k = 1; k <= MAX_NUM; ++k) {
19                // Fetch the frequencies of the current combination
20                const freqI: number = frequency[i];
21                const freqJ: number = frequency[j];
22                const freqK: number = frequency[k];
23              
24                // Calculate the sum of the triplet
25                const sum: number = i + j + k;
26                // Count the number of divisors the sum has from the triplet
27                const divisorCount: number = (sum % i === 0 ? 1 : 0) 
28                                             + (sum % j === 0 ? 1 : 0) 
29                                             + (sum % k === 0 ? 1 : 0);
30              
31                // We are only interested in triplets where exactly one of the numbers divides the sum
32                if (divisorCount !== 1) continue;
33
34                // Handle the cases depending on the uniqueness of i, j, k
35                if (i === j && i === k) {
36                    // All numbers are the same (choose 3 from freqI)
37                    answer += freqI * (freqI - 1) * (freqI - 2) / 6;
38                } else if (i === j) {
39                    // i and j are the same, k is different (choose 2 from freqI and multiply by freqK)
40                    answer += freqI * (freqI - 1) / 2 * freqK;
41                } else if (i === k) {
42                    // i and k are the same, j is different (choose 2 from freqI and multiply by freqJ)
43                    answer += freqI * (freqI - 1) / 2 * freqJ;
44                } else if (j === k) {
45                    // j and k are the same, i is different (choose 2 from freqJ and multiply by freqI)
46                    answer += freqJ * (freqJ - 1) / 2 * freqI;
47                } else {
48                    // i, j, and k are all different (multiply counts of freqI, freqJ, freqK)
49                    answer += freqI * freqJ * freqK;
50                }
51            }
52        }
53    }
54
55    // Return the total count of single divisor triplets
56    return answer;
57}
58
59// Sample usage
60const sampleNums: number[] = [1, 2, 3];
61const sampleResult: number = singleDivisorTriplet(sampleNums);
62console.log(sampleResult);  // Output the result
63
Not Sure What to Study? Take the 2-min Quiz:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Time and Space Complexity

The given Python code aims to count the number of triplets in a list where exactly one of the three numbers is a divisor of the sum of the triplet.

Time Complexity:

The time complexity of the algorithm can be analyzed based on the nested loops and the operations performed within them.

We iterate over all unique elements in nums up to three times due to the nested loops. If there are k unique elements, then the triple-nested loop runs O(k^3) times. The check function is called inside the innermost loop, which operates in O(1) time because it simply performs a fixed series of modulo operations and comparisons.

For each unique triplet (a, b, c), we calculate the number of such triplets considering their counts in the original list. The increments to ans involve multiplication, which is an O(1) operation.

Therefore, the overall time complexity is O(k^3).

Space Complexity:

The space complexity can be determined by the additional space used by the algorithm, which is not part of the input.

  • The counter variable is a Counter object, which stores the frequency of each unique number in nums. It occupies O(k) space, where k is the number of unique numbers in nums.

  • Other than that, the space usage is constant O(1) because we only use a fixed number of extra variables (ans, a, b, c, cnt1, cnt2, cnt3, and space for the check function's return value).

Thus, the overall space complexity is O(k).

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

What is the space complexity of the following code?

1int sum(int n) {
2  if (n <= 0) {
3    return 0;
4  }
5  return n + sum(n - 1);
6}

Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫