982. Triples with Bitwise AND Equal To Zero
Problem Description
Given an array of integers nums
, we are tasked with finding the count of all unique "AND triples." An "AND triple" is defined as a combination of three indices (i, j, k)
within the bounds of the array nums
such that the following conditions are met:
0 <= i < nums.length
0 <= j < nums.length
0 <= k < nums.length
The key condition for an "AND triple" is that the bitwise AND operation (&
) applied to nums[i]
, nums[j]
, and nums[k]
results in zero, i.e., nums[i] & nums[j] & nums[k] == 0
.
The bitwise AND operation takes two numbers as operands and performs the AND operation on every pair of corresponding bits. The operation results in a 1 in each bit position for which the corresponding bits of both operands are 1s.
The goal is to write a function that will return the total number of such "AND triples."
Intuition
To solve the problem efficiently, we focus on the definition of the bitwise AND operator. When performing an AND operation where the result is 0, any bit in the resulting number must have been 0 in at least one of the operands. This means that as long as there is a zero at the same position in either i
, j
, or k
index of nums
, their AND operation will yield a zero.
An intuitive approach might be trying all possible triples (i, j, k)
and checking their AND operation one by one, but this would result in a time complexity of O(n^3), which is not efficient for large arrays.
Instead, we can take advantage of the commutative property of the AND operation (i.e., a & b = b & a
) and count the number of occurrences for every possible result of nums[i] & nums[j]
. We can precompute and store these counts in a Counter dictionary, which is a type of dictionary provided by Python's collections
module that counts the occurrences of each element.
After counting all the possible AND results of pairs (i, j)
, we iterate through each value z
in nums
and for each pair (xy, v)
in our Counter dictionary (where xy
is a result of nums[i] & nums[j]
and v
is the count of how many times this result occurred). If xy & z == 0
, it means this z
paired with all v
occurrences of xy
will contribute v
to the total count of "AND triples".
This approach significantly reduces the complexity of the problem by reducing the three-dimensional problem (checking all triples) into a two-dimensional problem (storing pairs) and then iterating through the array only once more, giving us an overall time complexity closer to O(n^2).
Solution Approach
The solution employs an efficient approach that involves a combination of precomputation and hash mapping through the Counter
class, which is part of Python's collections
module. The idea is to leverage the properties of the bitwise AND operation to reduce the computational complexity. Here's a breakdown of the implementation steps using the provided solution code:
-
Precomputation of Pairwise AND results: We create a
Counter
to precompute and store all possible bitwise AND resultsx & y
for each pair in the arraynums
. This precomputation is done in a nested loop wherex
andy
iterate over all elements innums
.cnt = Counter(x & y for x in nums for y in nums)
cnt
now holds the frequency of each result that can be obtained by performing the bitwise AND operation on any two elements innums
. -
Iterate Through the Counter and the Array: We iterate through both the Counter dictionary
cnt
and the arraynums
. For every combination ofxy
(a precomputed AND result fromcnt
) andz
(an element innums
), we check if the bitwise AND ofxy & z
equals 0.return sum(v for xy, v in cnt.items() for z in nums if xy & z == 0)
In this line of code,
xy
represents a possible result ofnums[i] & nums[j]
andv
is the number of times this result occurs (the count). We are interested in the cases wherexy & z
is zero because that means we've found a valid "AND triple".
In the above implementation, the complex part of the problem, which is finding pairs whose AND is 0, is handled by the Counter, which amortizes that work over all element pairs with a single pass through nums
. Then, the final loop provides a multiplication factor (v
) for each zero-resulting AND pair when checked against all elements of nums
. This clever use of hash mapping paired with the bitwise operation properties results in an elegant solution that is more computationally efficient than brute force.
Data Structures:
- Counter (hash map): A specialized dictionary for counting hashable objects, quite handy for counting occurrences of results (precomputed AND operations).
Algorithm Patterns:
- Hash mapping: To store and access the count of occurrences of pairwise AND results efficiently.
- Bitwise operations: Core part of the logic, determining the property of the "AND triple".
Complexity Analysis:
- Time Complexity: O(n^2), since we iterate over all pairs once to compute the AND results and store them in the Counter, and then iterate over this Counter and
nums
in a nested fashion. - Space Complexity: O(n^2), primarily due to the space required to store the Counter dictionary, which in the worst case could hold a distinct count for every pair combination.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small array nums = [2, 1, 4]
to illustrate the solution approach:
-
Precomputation of Pairwise AND results:
- We will calculate the bitwise AND for every pair of numbers in the array and count the occurrences of each result. For our example array
nums
, the pairs and their AND results are:2 & 2 = 2
2 & 1 = 0
(since the binary representation of 2 is10
and 1 is01
, the AND operation results in00
)2 & 4 = 0
(binary10
AND100
results in000
)1 & 1 = 1
1 & 4 = 0
(binary01
AND100
results in000
)4 & 4 = 4
- The frequency of AND results will then be stored in a Counter:
cnt
becomes{2: 1, 0: 3, 1: 1, 4: 1}
.
- We will calculate the bitwise AND for every pair of numbers in the array and count the occurrences of each result. For our example array
-
Iterate Through the Counter and the Array:
- With the Counter ready, we now iterate through each unique AND result stored in
cnt
and each elementz
innums
, and count if the AND of the stored result withz
equals 0:- For
xy = 2
: The AND operation with any ofnums
does not yield 0, so no count is added. - For
xy = 0
: The AND operation with all elements innums
(2, 1, 4) will yield 0, and sincecnt[0] = 3
, we have 3 valid "AND triples" for each element innums
, totaling 9. - For
xy = 1
: The AND operation with all elements innums
(2, 1, 4) does not yield 0, so no count is added. - For
xy = 4
: The AND operation with any ofnums
does not yield 0, so no count is added.
- For
- The count for this iteration will be
0 + 9 + 0 + 0 = 9
.
- With the Counter ready, we now iterate through each unique AND result stored in
Therefore, the total count of all unique "AND triples" for the array nums = [2, 1, 4]
is 9.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def countTriplets(self, nums: List[int]) -> int:
5 # Create a counter (dictionary) to store the frequency of each bitwise AND result.
6 # We calculate bitwise AND for all possible pairs of numbers in the nums list.
7 frequency_counter = Counter(x & y for x in nums for y in nums)
8
9 # Initialize a result variable to store the count of triplets.
10 triplet_count = 0
11
12 # Iterate through the items in the frequency counter. Each item is a tuple (bitwise result, frequency).
13 for bitwise_result, frequency in frequency_counter.items():
14 # For each bitwise AND result, we check if there's a number z in nums such that
15 # bitwise_result & z is equal to 0. If yes, we add the frequency of the bitwise result
16 # to the triplet_count since each occurrence contributes to a valid triplet.
17 for z in nums:
18 if bitwise_result & z == 0:
19 triplet_count += frequency
20
21 # After iterating through all items and nums, we return the count of triplets.
22 return triplet_count
23
1class Solution {
2 // Counts the triplets (x, y, z) such that (x & y & z) == 0 from the given nums array.
3 public int countTriplets(int[] nums) {
4 // Find the maximum value in nums to determine the size of the count array.
5 int maxVal = 0;
6 for (int num : nums) {
7 maxVal = Math.max(maxVal, num);
8 }
9
10 // Initialize the count array that will hold the frequency of occurrences of x & y.
11 int[] count = new int[maxVal + 1];
12 for (int x : nums) {
13 for (int y : nums) {
14 // Increment the count of x & y.
15 count[x & y]++;
16 }
17 }
18
19 // Initialize the answer to count the number of valid triplets.
20 int answer = 0;
21 // Iterate over all possible combinations of x & y and check with each z in nums.
22 for (int xy = 0; xy <= maxVal; ++xy) {
23 for (int z : nums) {
24 // If x & y & z == 0, add the count of x & y to answer.
25 if ((xy & z) == 0) {
26 answer += count[xy];
27 }
28 }
29 }
30
31 // Return the total count of valid triplets.
32 return answer;
33 }
34}
35
1#include <vector>
2#include <algorithm>
3#include <cstring>
4
5class Solution {
6public:
7 // Function to count the number of triplets (x, y, z) from the nums array such that
8 // x & y & z == 0, where & is the bitwise AND operator.
9 int countTriplets(vector<int>& nums) {
10 // Find the maximum element to determine the size of the count array.
11 int maxElement = *max_element(nums.begin(), nums.end());
12
13 // Create and initialize the count array to store the frequency
14 // of bitwise AND results of all possible pairs (x, y).
15 int count[maxElement + 1];
16 memset(count, 0, sizeof(count));
17
18 // Populate the count array with the frequency of all possible (x & y) results.
19 for (int x : nums) {
20 for (int y : nums) {
21 count[x & y]++;
22 }
23 }
24
25 // Initialize the answer to count the number of valid triplets.
26 int answer = 0;
27
28 // Iterate over all possible values of (x & y) and every element z in nums.
29 for (int xy = 0; xy <= maxElement; ++xy) {
30 for (int z : nums) {
31 // Check if the current combination satisfies x & y & z == 0.
32 // If so, add the frequency of (x & y) to the answer.
33 if ((xy & z) == 0) {
34 answer += count[xy];
35 }
36 }
37 }
38
39 // Return the total count of valid triplets.
40 return answer;
41 }
42};
43
1function countTriplets(nums: number[]): number {
2 // Find the maximum number in the 'nums' array.
3 const maxNum = Math.max(...nums);
4 // Create an array to store the count of all possible 'x & y' results.
5 const count: number[] = Array(maxNum + 1).fill(0);
6
7 // Calculate the frequency of each 'x & y' result.
8 for (const x of nums) {
9 for (const y of nums) {
10 count[x & y]++;
11 }
12 }
13
14 // Initialize a variable to store the total count of triplets.
15 let tripletCount = 0;
16
17 // Iterate through all possible 'x & y' results.
18 for (let xy = 0; xy <= maxNum; ++xy) {
19 // For each element 'z' in 'nums',
20 // if 'xy & z' is zero, increment the total count of triplets by the pre-calculated frequency of 'xy'.
21 for (const z of nums) {
22 if ((xy & z) === 0) {
23 tripletCount += count[xy];
24 }
25 }
26 }
27
28 // Return the total count of triplets.
29 return tripletCount;
30}
31
Time and Space Complexity
Time Complexity
The time complexity of the provided code can be analyzed by breaking down the nested loops and operations:
- There are two nested loops that iterate over the array
nums
to compute bitwise AND (&) for every pair of elements. Ifn
is the length ofnums
, this results inn^2
iterations. - For each iteration, the bitwise AND operation is performed once, which takes constant time, so the nested loops contribute
O(n^2)
to the time complexity. - The count (
cnt
) is updated during these iterations, which, in the worst case, will storen^2
unique values if all possible AND operations result in unique values. Each update to theCounter
(a form of dictionary) takes an average ofO(1)
time. - The final loop iterates
v
times over the elementsz
innums
for eachxy
key-value pair incnt
. Ifv
represents the count of occurrences ofxy
, and there arem
such uniquexy
pairs (withm
being at mostn^2
), the final loop could iterate up tom * n
times. - The bitwise AND operation and comparison inside the final loop also take constant time.
Based on the information above, the total time complexity of this code is O(n^2) + O(m * n)
, which simplifies to O(n^2)
because m
is at most n^2
, and therefore m * n
is at most n^3
, which is dominated by the n^2
term when n
is large.
Space Complexity
For space complexity, we consider the additional space used apart from the input:
- The
Counter
object (cnt
) holds the result of bitwise AND operations for each pair of elements innums
, which can grow up ton^2
unique key-value pairs in the worst case. Hence, space complexity due tocnt
isO(n^2)
. - No other significant additional space is used that grows with the input size.
Thus, the overall space complexity of the code is O(n^2)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Which data structure is used to implement priority queue?
Recommended Readings
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!