982. Triples with Bitwise AND Equal To Zero
Problem Description
You are given an integer array nums
. Your task is to count the number of AND triples.
An AND triple is a set of three indices (i, j, k)
that satisfy the following conditions:
- All three indices
i
,j
, andk
must be valid positions in the array (between0
andnums.length - 1
) - The bitwise AND operation of the three elements at these positions must equal zero:
nums[i] & nums[j] & nums[k] == 0
The &
operator represents the bitwise AND operation, which compares each bit position of the numbers and returns 1
only if both bits are 1
, otherwise returns 0
.
For example, if nums = [2, 1, 3]
:
- The triple
(0, 1, 2)
gives us2 & 1 & 3 = 0
, so this is a valid AND triple - We need to check all possible combinations of three indices
Note that the indices don't need to be distinct - you can use the same index multiple times in a triple. For instance, (0, 0, 1)
is a valid triple if nums[0] & nums[0] & nums[1] == 0
.
Return the total count of all valid AND triples.
Intuition
The naive approach would be to check all possible triples (i, j, k)
using three nested loops, computing nums[i] & nums[j] & nums[k]
for each combination. However, this would require O(n³)
operations, which could be inefficient for larger arrays.
The key insight is that we can optimize this by breaking down the problem into two stages:
-
Pre-compute partial results: Instead of computing
nums[i] & nums[j] & nums[k]
for every triple, we can first compute all possible values ofnums[i] & nums[j]
for every pair(i, j)
. Since the bitwise AND operation is associative, we have(nums[i] & nums[j]) & nums[k] = nums[i] & nums[j] & nums[k]
. -
Count occurrences: We store how many times each partial result
nums[i] & nums[j]
appears. This is important because if multiple pairs produce the same AND result, they will all contribute to the answer when combined with a suitable third element. -
Match with third element: For each unique partial result
xy
from step 1, we check against every elementz
in the array. Ifxy & z == 0
, then all pairs that producedxy
can form valid triples with thisz
. So we add the count of such pairs to our answer.
This approach is more efficient because:
- We avoid redundant calculations by storing intermediate results
- We leverage counting to handle multiple occurrences of the same partial AND result
- We effectively reduce the problem from checking
n³
combinations ton²
for pre-computation plus checking at mostn² × n
conditions (but often much fewer due to the counting optimization)
The beauty of this solution lies in recognizing that many different pairs (i, j)
might produce the same AND result, and all of them would behave identically when combined with any third element k
.
Solution Approach
The implementation follows a two-phase approach using enumeration and counting:
Phase 1: Pre-compute all pairwise AND results
cnt = Counter(x & y for x in nums for y in nums)
- We use a nested loop (via list comprehension) to iterate through all pairs of elements in
nums
- For each pair
(x, y)
, we compute their bitwise AND:x & y
- We use Python's
Counter
from the collections module to count how many times each AND result appears - For example, if
nums = [2, 1, 3]
, we compute:2 & 2 = 2
,2 & 1 = 0
,2 & 3 = 2
1 & 2 = 0
,1 & 1 = 1
,1 & 3 = 1
3 & 2 = 2
,3 & 1 = 1
,3 & 3 = 3
- The counter would store:
{2: 3, 0: 2, 1: 3, 3: 1}
Phase 2: Count valid triples
return sum(v for xy, v in cnt.items() for z in nums if xy & z == 0)
- We iterate through each unique pairwise AND result
xy
and its countv
from our counter - For each
xy
, we check every elementz
in the original array - If
xy & z == 0
, this means we found valid triples:- All
v
pairs that produced the AND resultxy
can form valid triples with thisz
- We add
v
to our running total
- All
- The sum accumulates all valid triple counts
Why this works:
- If
(nums[i] & nums[j]) & nums[k] == 0
, then any indices(i', j')
wherenums[i'] & nums[j'] == nums[i] & nums[j]
will also satisfy(nums[i'] & nums[j']) & nums[k] == 0
- By grouping pairs by their AND result and counting them, we avoid redundant calculations
- Time complexity:
O(n²)
for the first phase +O(n × unique_AND_results)
for the second phase, which is better than the naiveO(n³)
approach
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 1, 3]
.
Step 1: Pre-compute all pairwise AND results
We need to compute x & y
for every pair of elements (including pairs with the same element):
-
For
x = 2
(binary: 10):2 & 2 = 2
(10 & 10 = 10)2 & 1 = 0
(10 & 01 = 00)2 & 3 = 2
(10 & 11 = 10)
-
For
x = 1
(binary: 01):1 & 2 = 0
(01 & 10 = 00)1 & 1 = 1
(01 & 01 = 01)1 & 3 = 1
(01 & 11 = 01)
-
For
x = 3
(binary: 11):3 & 2 = 2
(11 & 10 = 10)3 & 1 = 1
(11 & 01 = 01)3 & 3 = 3
(11 & 11 = 11)
Our Counter becomes: {0: 2, 1: 3, 2: 3, 3: 1}
- AND result
0
appears 2 times (from pairs at indices (0,1) and (1,0)) - AND result
1
appears 3 times (from pairs at indices (1,1), (1,2), (2,1)) - AND result
2
appears 3 times (from pairs at indices (0,0), (0,2), (2,0)) - AND result
3
appears 1 time (from pair at indices (2,2))
Step 2: Count valid triples
Now for each unique AND result and its count, check against every element in nums
:
-
For
xy = 0
(count = 2):- Check
0 & 2 = 0
✓ → Add 2 to answer - Check
0 & 1 = 0
✓ → Add 2 to answer - Check
0 & 3 = 0
✓ → Add 2 to answer - Subtotal: 6
- Check
-
For
xy = 1
(count = 3):- Check
1 & 2 = 0
✓ → Add 3 to answer - Check
1 & 1 = 1
✗ → Add 0 - Check
1 & 3 = 1
✗ → Add 0 - Subtotal: 3
- Check
-
For
xy = 2
(count = 3):- Check
2 & 2 = 2
✗ → Add 0 - Check
2 & 1 = 0
✓ → Add 3 to answer - Check
2 & 3 = 2
✗ → Add 0 - Subtotal: 3
- Check
-
For
xy = 3
(count = 1):- Check
3 & 2 = 2
✗ → Add 0 - Check
3 & 1 = 1
✗ → Add 0 - Check
3 & 3 = 3
✗ → Add 0 - Subtotal: 0
- Check
Total: 6 + 3 + 3 + 0 = 12
This means there are 12 valid AND triples where nums[i] & nums[j] & nums[k] == 0
.
Solution Implementation
1class Solution:
2 def countTriplets(self, nums: List[int]) -> int:
3 # Step 1: Pre-compute frequency of all possible bitwise AND results for pairs
4 # For each pair (x, y) from nums, calculate x & y and count occurrences
5 pair_and_frequency = Counter(x & y for x in nums for y in nums)
6
7 # Step 2: Count valid triplets
8 # For each pre-computed pair AND result and its frequency,
9 # check against each number z in nums
10 # If (x & y) & z == 0, then the triplet (x, y, z) has AND result of 0
11 triplet_count = sum(
12 frequency
13 for pair_and_result, frequency in pair_and_frequency.items()
14 for z in nums
15 if pair_and_result & z == 0
16 )
17
18 return triplet_count
19
1class Solution {
2 /**
3 * Counts the number of triplets (i, j, k) where nums[i] & nums[j] & nums[k] == 0.
4 * Uses a two-step approach: first precompute all possible pairs' AND results,
5 * then check which values when ANDed with the precomputed results give 0.
6 *
7 * @param nums the input array of integers
8 * @return the count of valid triplets
9 */
10 public int countTriplets(int[] nums) {
11 // Find the maximum value in the array to determine the size of frequency array
12 int maxValue = 0;
13 for (int num : nums) {
14 maxValue = Math.max(maxValue, num);
15 }
16
17 // Create frequency array to store count of all possible AND results of pairs
18 // frequencyCount[i] represents how many pairs (x, y) have x & y = i
19 int[] frequencyCount = new int[maxValue + 1];
20
21 // Precompute all possible pairs' AND results and their frequencies
22 // This handles the first two elements of the triplet
23 for (int firstNum : nums) {
24 for (int secondNum : nums) {
25 int andResult = firstNum & secondNum;
26 frequencyCount[andResult]++;
27 }
28 }
29
30 // Count valid triplets by checking each precomputed pair result with third element
31 int tripletCount = 0;
32
33 // Iterate through all possible pair AND results
34 for (int pairAndResult = 0; pairAndResult <= maxValue; pairAndResult++) {
35 // Check if this pair result ANDed with any third element gives 0
36 for (int thirdNum : nums) {
37 if ((pairAndResult & thirdNum) == 0) {
38 // Add the frequency of this pair result to the total count
39 tripletCount += frequencyCount[pairAndResult];
40 }
41 }
42 }
43
44 return tripletCount;
45 }
46}
47
1class Solution {
2public:
3 int countTriplets(vector<int>& nums) {
4 // Find the maximum value in the array to determine the upper bound
5 int maxValue = ranges::max(nums);
6
7 // Create a frequency array to store count of all possible AND results
8 // between pairs of elements from nums
9 int frequency[maxValue + 1];
10 memset(frequency, 0, sizeof(frequency));
11
12 // Count occurrences of all possible (nums[i] & nums[j]) values
13 // This preprocessing step calculates AND for all pairs
14 for (int& num1 : nums) {
15 for (int& num2 : nums) {
16 frequency[num1 & num2]++;
17 }
18 }
19
20 // Count valid triplets where (nums[i] & nums[j] & nums[k]) == 0
21 int result = 0;
22
23 // Iterate through all possible AND values from the frequency array
24 for (int andResult = 0; andResult <= maxValue; ++andResult) {
25 // For each element in nums, check if it forms a valid triplet
26 // with the current AND result
27 for (int& num : nums) {
28 // If (andResult & num) == 0, then all triplets with
29 // (nums[i] & nums[j]) == andResult and nums[k] == num
30 // will satisfy the condition
31 if ((andResult & num) == 0) {
32 result += frequency[andResult];
33 }
34 }
35 }
36
37 return result;
38 }
39};
40
1/**
2 * Counts the number of triplets (i, j, k) where nums[i] & nums[j] & nums[k] == 0
3 * @param nums - Array of integers to find triplets from
4 * @returns Number of valid triplets where bitwise AND equals 0
5 */
6function countTriplets(nums: number[]): number {
7 // Find the maximum value in the array to determine the upper bound
8 const maxValue: number = Math.max(...nums);
9
10 // Create a frequency array to store counts of all possible AND results between pairs
11 // countArray[value] = number of pairs (i, j) where nums[i] & nums[j] = value
12 const countArray: number[] = Array(maxValue + 1).fill(0);
13
14 // Calculate all possible AND results for pairs of elements
15 // This pre-computation avoids redundant calculations later
16 for (const firstNum of nums) {
17 for (const secondNum of nums) {
18 countArray[firstNum & secondNum]++;
19 }
20 }
21
22 // Initialize the result counter for valid triplets
23 let result: number = 0;
24
25 // Iterate through all possible AND values from pairs
26 for (let pairAndValue: number = 0; pairAndValue <= maxValue; ++pairAndValue) {
27 // For each third element, check if it forms a valid triplet
28 for (const thirdNum of nums) {
29 // If (nums[i] & nums[j]) & nums[k] == 0, we found valid triplets
30 if ((pairAndValue & thirdNum) === 0) {
31 // Add the count of all pairs that have this AND value
32 result += countArray[pairAndValue];
33 }
34 }
35 }
36
37 return result;
38}
39
Time and Space Complexity
The time complexity is O(n^2 + n × M)
, where n
is the length of the array nums
and M
is the maximum value in the array nums
(with M ≤ 2^16
in this problem).
Breaking down the time complexity:
- The first part
Counter(x & y for x in nums for y in nums)
iterates through all pairs of elements innums
, performing a bitwise AND operation for each pair. This takesO(n^2)
time. - The second part iterates through all key-value pairs in the counter (at most
M
unique values from the bitwise AND operations) and for each pair, iterates through all elementsz
innums
to check ifxy & z == 0
. This takesO(M × n)
time. - Combined:
O(n^2 + n × M)
The space complexity is O(M)
, where M
is the maximum possible value in nums
.
The space is primarily used by the Counter
object cnt
, which stores the frequency of each unique result from x & y
. In the worst case, there could be up to M
different values resulting from the bitwise AND operations between pairs of elements, hence O(M)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the index requirement - treating indices as distinct
A common mistake is assuming that the three indices i
, j
, and k
must all be different. The problem actually allows using the same index multiple times. For instance, (0, 0, 0)
is a valid triple if nums[0] & nums[0] & nums[0] == 0
.
Wrong approach:
# Incorrectly filtering out cases where indices are the same
count = 0
for i in range(len(nums)):
for j in range(i+1, len(nums)): # Wrong: skipping i == j
for k in range(j+1, len(nums)): # Wrong: skipping j == k
if nums[i] & nums[j] & nums[k] == 0:
count += 1
Correct approach:
# Allow all index combinations including duplicates for x in nums: for y in nums: for z in nums: if x & y & z == 0: count += 1
Pitfall 2: Integer overflow concerns in other languages
While Python handles arbitrary-precision integers automatically, implementing this in languages like C++ or Java requires attention to data types. The bitwise AND operation itself won't overflow, but the counting might.
Solution for other languages:
- Use appropriate data types (e.g.,
long long
in C++ orlong
in Java) for the counter - The maximum count would be
n³
where n is the array length, so ensure your counter variable can handle this
Pitfall 3: Inefficient Counter implementation
Some might try to manually implement the counting logic instead of using Python's efficient Counter
class, leading to verbose and potentially slower code.
Inefficient manual approach:
# Manual dictionary management pair_and_frequency = {} for x in nums: for y in nums: result = x & y if result in pair_and_frequency: pair_and_frequency[result] += 1 else: pair_and_frequency[result] = 1
Better approach (as shown in solution):
# Concise and efficient using Counter pair_and_frequency = Counter(x & y for x in nums for y in nums)
Pitfall 4: Attempting premature optimization with bit manipulation tricks
Some might try to optimize by analyzing bit patterns or using advanced bit manipulation techniques, but this often complicates the code without significant performance gains for this problem's constraints.
Example of over-optimization:
# Trying to skip combinations based on bit patterns
# This adds complexity without meaningful performance improvement
max_val = max(nums)
bit_positions = []
while max_val:
bit_positions.append(max_val & 1)
max_val >>= 1
# ... complex logic to prune combinations
The straightforward two-phase approach with O(n²)
complexity is already optimal enough for typical constraints and much clearer to understand and maintain.
Which data structure is used to implement recursion?
Recommended Readings
Coding Interview 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
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 assets algo monster recursion jpg You first call Ben and ask
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!