1711. Count Good Meals
Problem Description
You need to find pairs of food items whose combined deliciousness equals a power of 2 (like 1, 2, 4, 8, 16, 32, etc.).
Given an array deliciousness
where each element represents the deliciousness value of a food item, you need to count how many different pairs of food items can form a "good meal". A good meal consists of exactly two different food items whose deliciousness values sum to a power of 2.
Key points to understand:
- You must pick exactly 2 different food items (different indices)
- Their sum must equal a power of 2 (e.g.,
2^0 = 1
,2^1 = 2
,2^2 = 4
, etc.) - Items at different positions in the array are considered different, even if they have the same deliciousness value
- Return the total count modulo
10^9 + 7
For example, if you have deliciousness values [1, 3, 5, 7, 9]
:
- Items at index 0 (value 1) and index 1 (value 3) sum to 4, which is
2^2
, so this is a good meal - Items at index 0 (value 1) and index 3 (value 7) sum to 8, which is
2^3
, so this is also a good meal - And so on...
The solution uses a hash table to track previously seen values and checks for each new element whether it can form a power of 2 with any previously seen element. For each element d
, it checks all possible powers of 2 up to the maximum possible sum and looks for the complement (power_of_2 - d)
in the hash table.
Intuition
The naive approach would be to check every pair of food items to see if their sum is a power of 2, but with up to 10^5
items, this O(n^2)
approach would be too slow.
The key insight is that powers of 2 are limited. Since the maximum deliciousness value is at most 2^20
, the maximum possible sum of two items is 2^20 + 2^20 = 2^21
. This means we only need to check about 22 different power-of-2 values.
Instead of checking if the sum of every pair is a power of 2, we can flip the problem: for each food item with deliciousness d
, we can check if there exists another item that would sum with d
to make a specific power of 2.
For example, if we have a food item with deliciousness 3
, and we want to check if it can form a sum of 8
(which is 2^3
), we need to find if there's another item with deliciousness 8 - 3 = 5
.
This leads us to use a hash table to track the frequency of deliciousness values we've already seen. As we iterate through the array:
- For the current item
d
, we check all possible powers of 2 (from1
up to twice the maximum value) - For each power of 2 value
s
, we check ifs - d
exists in our hash table - If it exists, we've found valid pairs - the count is how many times
s - d
appeared before - After checking all powers of 2 for the current item, we add it to our hash table for future items to pair with
This approach is efficient because:
- We only traverse the array once:
O(n)
- For each element, we only check about 22 powers of 2:
O(log M)
whereM
is the maximum value - Hash table lookups are
O(1)
The total time complexity becomes O(n × log M)
, which is much better than the naive O(n^2)
.
Solution Approach
The solution uses a hash table combined with enumeration of powers of 2. Here's how the implementation works:
Data Structure Used:
- A
Counter
(hash table) to track the frequency of each deliciousness value we've encountered
Algorithm Steps:
-
Initialize variables:
mod = 10^9 + 7
for the modulo operationmx = max(deliciousness) << 1
calculates twice the maximum value (left shift by 1 is equivalent to multiplying by 2). This represents the maximum possible sum we need to checkcnt = Counter()
creates an empty hash table to store frequenciesans = 0
to accumulate the count of valid pairs
-
Main iteration through the array: For each deliciousness value
d
:a. Check all powers of 2:
- Start with
s = 1
(which is2^0
) - While
s <= mx
:- Check if
s - d
exists in the hash table - If it exists, add
cnt[s - d]
to the answer (this represents all the previous items that can pair withd
to form sums
) - Double
s
by left shifting (s <<= 1
) to get the next power of 2
- Check if
b. Update the hash table:
- After checking all possible powers of 2 for the current element, increment
cnt[d]
by 1 - This ensures the current element can be paired with future elements
- Start with
-
Return the result:
- Return
ans % mod
- Return
Why this order matters:
- We update the hash table after checking for pairs to avoid counting a pair twice
- By processing elements one by one and only looking at previously seen elements, we ensure each pair
(i, j)
wherei < j
is counted exactly once
Example walkthrough:
If deliciousness = [1, 3, 5, 7, 9]
:
- Process
1
: No previous elements, add1
to hash table - Process
3
: Check if1-3=-2
,2-3=-1
,4-3=1
(found!),8-3=5
, etc. Find1
in hash table, soans += 1
- Process
5
: Check powers of 2, find8-5=3
exists, soans += 1
- And so on...
The time complexity is O(n × log M)
where n
is the array length and M
is the maximum value, since for each element we check at most log M
powers of 2.
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 a small example with deliciousness = [1, 3, 5, 7, 9]
.
Initial Setup:
mx = max(1,3,5,7,9) × 2 = 9 × 2 = 18
cnt = {}
(empty hash table)ans = 0
Step 1: Process element 1
- Check powers of 2: 1, 2, 4, 8, 16
1 - 1 = 0
: not in cnt2 - 1 = 1
: not in cnt4 - 1 = 3
: not in cnt8 - 1 = 7
: not in cnt16 - 1 = 15
: not in cnt
- Update cnt:
{1: 1}
- ans remains 0
Step 2: Process element 3
- Check powers of 2: 1, 2, 4, 8, 16
1 - 3 = -2
: negative, skip2 - 3 = -1
: negative, skip4 - 3 = 1
: found in cnt! ans += cnt[1] = 18 - 3 = 5
: not in cnt16 - 3 = 13
: not in cnt
- Update cnt:
{1: 1, 3: 1}
- ans = 1 (pair: indices 0,1 with values 1+3=4)
Step 3: Process element 5
- Check powers of 2: 1, 2, 4, 8, 16
1 - 5 = -4
: negative, skip2 - 5 = -3
: negative, skip4 - 5 = -1
: negative, skip8 - 5 = 3
: found in cnt! ans += cnt[3] = 116 - 5 = 11
: not in cnt
- Update cnt:
{1: 1, 3: 1, 5: 1}
- ans = 2 (new pair: indices 1,2 with values 3+5=8)
Step 4: Process element 7
- Check powers of 2: 1, 2, 4, 8, 16
1 - 7 = -6
: negative, skip2 - 7 = -5
: negative, skip4 - 7 = -3
: negative, skip8 - 7 = 1
: found in cnt! ans += cnt[1] = 116 - 7 = 9
: not in cnt
- Update cnt:
{1: 1, 3: 1, 5: 1, 7: 1}
- ans = 3 (new pair: indices 0,3 with values 1+7=8)
Step 5: Process element 9
- Check powers of 2: 1, 2, 4, 8, 16
1 - 9 = -8
: negative, skip2 - 9 = -7
: negative, skip4 - 9 = -5
: negative, skip8 - 9 = -1
: negative, skip16 - 9 = 7
: found in cnt! ans += cnt[7] = 1
- Update cnt:
{1: 1, 3: 1, 5: 1, 7: 1, 9: 1}
- ans = 4 (new pair: indices 3,4 with values 7+9=16)
Final Result: 4 good meals
- (1, 3) → 4 = 2²
- (3, 5) → 8 = 2³
- (1, 7) → 8 = 2³
- (7, 9) → 16 = 2⁴
Solution Implementation
1class Solution:
2 def countPairs(self, deliciousness: List[int]) -> int:
3 """
4 Count pairs of meals where the sum is a power of two.
5
6 Args:
7 deliciousness: List of integers representing deliciousness values
8
9 Returns:
10 Number of valid pairs modulo 10^9 + 7
11 """
12 MOD = 10**9 + 7
13
14 # Maximum possible sum is twice the maximum element
15 max_sum = max(deliciousness) * 2
16
17 # Dictionary to count occurrences of each deliciousness value
18 count_map = Counter()
19 result = 0
20
21 # Process each deliciousness value
22 for current_value in deliciousness:
23 # Check all powers of 2 up to max_sum
24 power_of_two = 1
25 while power_of_two <= max_sum:
26 # Find complement that would sum to power_of_two
27 complement = power_of_two - current_value
28
29 # Add count of complements seen so far
30 result = (result + count_map[complement]) % MOD
31
32 # Move to next power of 2
33 power_of_two <<= 1
34
35 # Add current value to the count map for future pairs
36 count_map[current_value] += 1
37
38 return result
39
1class Solution {
2 // Modulo value for preventing integer overflow
3 private static final int MOD = (int) 1e9 + 7;
4
5 public int countPairs(int[] deliciousness) {
6 // Find the maximum value and multiply by 2 to get the upper bound of possible power-of-2 sums
7 int maxValue = Arrays.stream(deliciousness).max().getAsInt();
8 int maxPossibleSum = maxValue << 1; // Equivalent to maxValue * 2
9
10 // Initialize the result counter
11 int result = 0;
12
13 // HashMap to store the frequency of each deliciousness value encountered so far
14 Map<Integer, Integer> frequencyMap = new HashMap<>();
15
16 // Iterate through each deliciousness value
17 for (int currentDeliciousness : deliciousness) {
18 // Check all possible powers of 2 up to maxPossibleSum
19 for (int powerOfTwo = 1; powerOfTwo <= maxPossibleSum; powerOfTwo <<= 1) {
20 // Calculate the complement needed to form a power of 2 sum
21 int complement = powerOfTwo - currentDeliciousness;
22
23 // Add the frequency of the complement to the result
24 // This counts how many previous elements can pair with current element
25 result = (result + frequencyMap.getOrDefault(complement, 0)) % MOD;
26 }
27
28 // Update the frequency map with the current deliciousness value
29 // If key exists, increment its value by 1; otherwise, set it to 1
30 frequencyMap.merge(currentDeliciousness, 1, Integer::sum);
31 }
32
33 return result;
34 }
35}
36
1class Solution {
2public:
3 // Define modulo constant for preventing integer overflow
4 const int MOD = 1e9 + 7;
5
6 int countPairs(vector<int>& deliciousness) {
7 // Find the maximum possible sum by doubling the largest element
8 // This sets the upper bound for power of two sums to check
9 int maxSum = *max_element(deliciousness.begin(), deliciousness.end()) << 1;
10
11 // Hash map to store frequency of each deliciousness value encountered so far
12 unordered_map<int, int> frequencyMap;
13
14 // Initialize result counter for valid pairs
15 int result = 0;
16
17 // Iterate through each element in the deliciousness array
18 for (auto& currentValue : deliciousness) {
19 // Check all possible powers of 2 up to maxSum
20 for (int powerOfTwo = 1; powerOfTwo <= maxSum; powerOfTwo <<= 1) {
21 // Calculate the complement needed to form a power of 2 sum
22 int complement = powerOfTwo - currentValue;
23
24 // Add count of complements found so far to result
25 // Use modulo to prevent overflow
26 result = (result + frequencyMap[complement]) % MOD;
27 }
28
29 // Increment frequency count for current value
30 // This ensures we only count pairs with elements before current position
31 ++frequencyMap[currentValue];
32 }
33
34 return result;
35 }
36};
37
1// Define modulo constant for preventing integer overflow
2const MOD = 1e9 + 7;
3
4function countPairs(deliciousness: number[]): number {
5 // Find the maximum possible sum by doubling the largest element
6 // This sets the upper bound for power of two sums to check
7 const maxSum = Math.max(...deliciousness) << 1;
8
9 // Hash map to store frequency of each deliciousness value encountered so far
10 const frequencyMap: Map<number, number> = new Map();
11
12 // Initialize result counter for valid pairs
13 let result = 0;
14
15 // Iterate through each element in the deliciousness array
16 for (const currentValue of deliciousness) {
17 // Check all possible powers of 2 up to maxSum
18 for (let powerOfTwo = 1; powerOfTwo <= maxSum; powerOfTwo <<= 1) {
19 // Calculate the complement needed to form a power of 2 sum
20 const complement = powerOfTwo - currentValue;
21
22 // Add count of complements found so far to result
23 // Use modulo to prevent overflow
24 result = (result + (frequencyMap.get(complement) || 0)) % MOD;
25 }
26
27 // Increment frequency count for current value
28 // This ensures we only count pairs with elements before current position
29 frequencyMap.set(currentValue, (frequencyMap.get(currentValue) || 0) + 1);
30 }
31
32 return result;
33}
34
Time and Space Complexity
Time Complexity: O(n * log(max_value))
- The outer loop iterates through all
n
elements in thedeliciousness
array - For each element
d
, the inner while loop runs whiles <= mx
, wheremx = max(deliciousness) * 2
- Since
s
starts at 1 and doubles each iteration (s <<= 1
), it takesO(log(mx))
iterations to reachmx
- The maximum value in the array determines
mx
, so we can say the inner loop runsO(log(max_value))
times - Operations inside the loops (Counter lookup and addition) are
O(1)
- Total time complexity:
O(n * log(max_value))
Space Complexity: O(n)
- The
Counter
objectcnt
stores at mostn
unique elements from thedeliciousness
array - Each unique value appears as a key in the counter with its frequency as the value
- Other variables (
mod
,mx
,ans
,d
,s
) use constant spaceO(1)
- Total space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Counting Pairs Twice
Pitfall: A common mistake is to iterate through all elements and check all other elements, which would count each pair twice (once as (i,j) and once as (j,i)).
Wrong Approach:
# This counts each pair twice!
for i in range(len(deliciousness)):
for j in range(len(deliciousness)):
if i != j and is_power_of_two(deliciousness[i] + deliciousness[j]):
result += 1
Solution: Process elements sequentially and only look at previously seen elements in the hash table. Update the hash table after checking, not before.
2. Incorrect Maximum Sum Calculation
Pitfall: Using just the maximum element value as the upper bound for powers of 2, which would miss valid pairs.
Wrong:
max_sum = max(deliciousness) # Wrong! Misses pairs like [2^20, 2^20]
Correct:
max_sum = max(deliciousness) * 2 # Correct upper bound
3. Forgetting Modulo Operation
Pitfall: Only applying modulo at the final return, which can cause integer overflow in languages with fixed integer sizes.
Wrong:
result = result + count_map[complement] # Can overflow return result % MOD # Too late!
Correct:
result = (result + count_map[complement]) % MOD # Apply modulo immediately
4. Using Set Instead of Counter
Pitfall: Using a set to track seen values loses frequency information, making it impossible to count multiple occurrences correctly.
Wrong:
seen = set()
for d in deliciousness:
# This only counts unique values, not frequencies!
if complement in seen:
result += 1
seen.add(d)
Correct: Use Counter or a dictionary to track frequencies of each value.
5. Checking Non-Powers of 2
Pitfall: Iterating through all numbers up to max_sum instead of just powers of 2, causing time limit exceeded.
Wrong:
for possible_sum in range(1, max_sum + 1):
if is_power_of_two(possible_sum): # Inefficient!
complement = possible_sum - current_value
Correct:
power_of_two = 1 while power_of_two <= max_sum: complement = power_of_two - current_value # ... check complement ... power_of_two <<= 1 # Efficiently jump to next power of 2
6. Including Negative Complements
Pitfall: Not handling cases where the complement would be negative (when current value > power of 2).
Note: While Python's Counter handles negative keys gracefully by returning 0, it's good practice to be aware that power_of_two - current_value
can be negative, and in some implementations, you might want to add a check:
if complement >= 0 and complement in count_map: result += count_map[complement]
Which data structure is used to implement priority queue?
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!