2588. Count the Number of Beautiful Subarrays
Problem Description
You have an integer array nums
(0-indexed). You can perform a special operation on this array:
- Pick two different indices
i
andj
from the array - Find a bit position
k
where bothnums[i]
andnums[j]
have a1
in their binary representation at that position (0-indexed from right) - Subtract
2^k
from bothnums[i]
andnums[j]
A subarray is called beautiful if you can make all its elements equal to 0
by applying this operation any number of times (including zero times).
Your task is to count how many beautiful subarrays exist in the given array.
Key observations:
- The operation essentially "turns off" a specific bit that's
1
in both selected numbers - For a subarray to be reducible to all zeros, every bit position across all numbers must have an even count of
1
s (so they can be paired off and eliminated) - If a bit appears an odd number of times, it cannot be eliminated since the operation requires pairing
Example understanding:
If you have [5, 3, 2]
which is [101, 011, 010]
in binary:
- Bit 0: appears in positions 0 and 1 (even count ✓)
- Bit 1: appears in positions 1 and 2 (even count ✓)
- Bit 2: appears in position 0 only (odd count ✗)
This subarray wouldn't be beautiful because bit 2 appears an odd number of times.
The solution uses XOR properties: if two prefix XOR values are the same, the subarray between them has all bits appearing an even number of times (since XOR of identical values is 0, meaning all bits canceled out evenly).
Intuition
Let's think about what makes a subarray "beautiful". We need to pair up 1
s at each bit position to eliminate them. This means each bit position must have an even count of 1
s across all numbers in the subarray.
Here's the key insight: XOR operation perfectly captures this "even/odd" property! When we XOR numbers:
- XORing two
1
s gives0
(even count → eliminated) - XORing a single
1
gives1
(odd count → remains)
So if we XOR all numbers in a subarray and get 0
, it means every bit position has an even count - exactly what we need for a beautiful subarray!
Now, how do we efficiently check all subarrays? Instead of checking each subarray individually (which would be O(n²)), we can use prefix XOR. If we maintain a running XOR from the start:
prefix[i]
= XOR of all elements from index0
toi
- For any subarray from index
i+1
toj
: its XOR equalsprefix[j] ^ prefix[i]
The magic happens when prefix[j] ^ prefix[i] = 0
, which means prefix[j] = prefix[i]
. So we're looking for positions where the prefix XOR repeats!
This transforms our problem into: "Count pairs of indices with the same prefix XOR value."
We use a hash table to track how many times each prefix XOR value has appeared. For each new position:
- Calculate the current prefix XOR
- Count how many times we've seen this value before (these form beautiful subarrays ending at current position)
- Add this prefix XOR to our hash table for future positions
Starting with {0: 1}
handles the edge case where the entire prefix from the start forms a beautiful subarray (when current prefix XOR is 0
).
Learn more about Prefix Sum patterns.
Solution Approach
We implement the solution using prefix XOR combined with a hash table to efficiently count beautiful subarrays.
Algorithm Steps:
-
Initialize the hash table: Start with
cnt = {0: 1}
. This means we've seen the XOR value0
once before we start (representing an empty prefix). This handles cases where a prefix from the start forms a beautiful subarray. -
Initialize variables:
ans = 0
to store our count of beautiful subarraysmask = 0
to maintain the running XOR (prefix XOR)
-
Traverse the array: For each element
x
innums
:- Update prefix XOR:
mask ^= x
- this gives us the XOR of all elements from index0
to current position - Count beautiful subarrays:
ans += cnt[mask]
- if we've seen thismask
valuek
times before, it means there arek
subarrays ending at the current position that are beautiful - Update hash table:
cnt[mask] += 1
- record that we've seen this prefix XOR value one more time
- Update prefix XOR:
-
Return the result:
ans
contains the total count of beautiful subarrays
Why this works:
- When
mask
at positionj
equalsmask
at positioni
(wherei < j
), the subarray fromi+1
toj
has XOR =0
- XOR =
0
means all bits appear an even number of times in that subarray - Even bit counts mean we can pair and eliminate all bits using the given operation
- Therefore, the subarray is beautiful
Time Complexity: O(n)
where n
is the length of the array - we traverse once
Space Complexity: O(n)
for the hash table in worst case (all different prefix XOR values)
The elegance of this solution lies in recognizing that the "pairing" nature of the operation translates perfectly to the XOR operation, allowing us to reduce a seemingly complex problem to finding matching prefix XOR values.
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 algorithm with nums = [4, 3, 1, 2, 4]
.
First, let's understand what we're looking for. In binary:
- 4 = 100
- 3 = 011
- 1 = 001
- 2 = 010
- 4 = 100
Step-by-step execution:
Initialization:
cnt = {0: 1}
(we've "seen" XOR value 0 once before starting)ans = 0
(no beautiful subarrays found yet)mask = 0
(running XOR value)
Index 0 (x = 4):
mask = 0 ^ 4 = 4
(binary: 100)- Check
cnt[4]
= 0 (haven't seen mask=4 before) ans = 0 + 0 = 0
- Update
cnt[4] = 1
- State:
cnt = {0: 1, 4: 1}
,ans = 0
Index 1 (x = 3):
mask = 4 ^ 3 = 7
(binary: 111)- Check
cnt[7]
= 0 (haven't seen mask=7 before) ans = 0 + 0 = 0
- Update
cnt[7] = 1
- State:
cnt = {0: 1, 4: 1, 7: 1}
,ans = 0
Index 2 (x = 1):
mask = 7 ^ 1 = 6
(binary: 110)- Check
cnt[6]
= 0 (haven't seen mask=6 before) ans = 0 + 0 = 0
- Update
cnt[6] = 1
- State:
cnt = {0: 1, 4: 1, 7: 1, 6: 1}
,ans = 0
Index 3 (x = 2):
mask = 6 ^ 2 = 4
(binary: 100)- Check
cnt[4]
= 1 (we've seen mask=4 once at index 0!) ans = 0 + 1 = 1
✓ Found beautiful subarray[3, 1, 2]
- Update
cnt[4] = 2
- State:
cnt = {0: 1, 4: 2, 7: 1, 6: 1}
,ans = 1
Index 4 (x = 4):
mask = 4 ^ 4 = 0
(binary: 000)- Check
cnt[0]
= 1 (we've seen mask=0 once - the initial value!) ans = 1 + 1 = 2
✓ Found beautiful subarray[4, 3, 1, 2, 4]
- Update
cnt[0] = 2
- Final state:
cnt = {0: 2, 4: 2, 7: 1, 6: 1}
,ans = 2
Result: 2 beautiful subarrays
Let's verify our found subarrays:
-
[3, 1, 2] (indices 1-3): XOR = 3 ^ 1 ^ 2 = 0 ✓
- In binary: 011 ^ 001 ^ 010 = 000
- All bits appear even times, so it's beautiful
-
[4, 3, 1, 2, 4] (indices 0-4): XOR = 4 ^ 3 ^ 1 ^ 2 ^ 4 = 0 ✓
- In binary: 100 ^ 011 ^ 001 ^ 010 ^ 100 = 000
- All bits appear even times, so it's beautiful
The algorithm correctly identifies when prefix XOR values repeat, indicating that the subarray between those positions has XOR = 0 and is therefore beautiful.
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def beautifulSubarrays(self, nums: List[int]) -> int:
6 # Initialize counter with 0:1 to handle subarrays starting from index 0
7 # Key: XOR prefix value, Value: count of occurrences
8 prefix_xor_count = Counter({0: 1})
9
10 # Initialize result counter and running XOR mask
11 beautiful_count = 0
12 current_xor = 0
13
14 # Iterate through each number in the array
15 for num in nums:
16 # Update the running XOR with current number
17 current_xor ^= num
18
19 # If we've seen this XOR value before, it means there are subarrays
20 # ending at current position with XOR sum of 0
21 # Add the count of previous occurrences to our result
22 beautiful_count += prefix_xor_count[current_xor]
23
24 # Increment the count for current XOR value
25 prefix_xor_count[current_xor] += 1
26
27 return beautiful_count
28
1class Solution {
2 public long beautifulSubarrays(int[] nums) {
3 // Map to store the frequency of each XOR prefix value
4 // Key: XOR prefix value, Value: frequency count
5 Map<Integer, Integer> prefixXorCount = new HashMap<>();
6
7 // Initialize with 0 having count 1 (empty prefix)
8 prefixXorCount.put(0, 1);
9
10 // Variable to store the total count of beautiful subarrays
11 long totalBeautifulSubarrays = 0;
12
13 // Running XOR value (prefix XOR)
14 int currentXor = 0;
15
16 // Iterate through each element in the array
17 for (int num : nums) {
18 // Update the running XOR with current element
19 currentXor ^= num;
20
21 // If currentXor exists in map, it means we found subarrays with XOR = 0
22 // The count of such subarrays equals the previous occurrences of this XOR value
23 // merge() returns the new value after incrementing, so we subtract 1 to get previous count
24 int previousCount = prefixXorCount.merge(currentXor, 1, Integer::sum) - 1;
25 totalBeautifulSubarrays += previousCount;
26 }
27
28 return totalBeautifulSubarrays;
29 }
30}
31
1class Solution {
2public:
3 long long beautifulSubarrays(vector<int>& nums) {
4 // HashMap to store frequency of each XOR prefix value
5 // Initialize with {0: 1} to handle subarrays starting from index 0
6 unordered_map<int, int> prefixXorFrequency{{0, 1}};
7
8 // Total count of beautiful subarrays
9 long long beautifulCount = 0;
10
11 // Running XOR value (prefix XOR from start to current position)
12 int currentXor = 0;
13
14 // Iterate through each number in the array
15 for (int num : nums) {
16 // Update the running XOR with current number
17 currentXor ^= num;
18
19 // If we've seen this XOR value before, it means there are subarrays
20 // ending at current position with XOR sum = 0
21 // Add the frequency of this XOR value to our count, then increment it
22 beautifulCount += prefixXorFrequency[currentXor]++;
23 }
24
25 return beautifulCount;
26 }
27};
28
1/**
2 * Counts the number of beautiful subarrays where XOR of all elements equals 0
3 * A subarray is beautiful if the XOR of all its elements is 0
4 *
5 * @param nums - The input array of integers
6 * @returns The count of beautiful subarrays
7 */
8function beautifulSubarrays(nums: number[]): number {
9 // Map to store frequency of each XOR prefix value
10 // Key: XOR prefix value, Value: count of occurrences
11 const prefixXorCount: Map<number, number> = new Map<number, number>();
12
13 // Initialize with 0 XOR value having count 1 (empty prefix)
14 prefixXorCount.set(0, 1);
15
16 // Total count of beautiful subarrays
17 let beautifulCount: number = 0;
18
19 // Running XOR value (prefix XOR)
20 let currentXor: number = 0;
21
22 // Iterate through each number in the array
23 for (const num of nums) {
24 // Update the running XOR with current number
25 currentXor ^= num;
26
27 // If we've seen this XOR value before, it means there are subarrays
28 // ending at current position with XOR = 0
29 // Add the count of previous occurrences to our result
30 beautifulCount += prefixXorCount.get(currentXor) || 0;
31
32 // Update the frequency map with current XOR value
33 prefixXorCount.set(currentXor, (prefixXorCount.get(currentXor) || 0) + 1);
34 }
35
36 return beautifulCount;
37}
38
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the array nums
exactly once. For each element in the array:
- XOR operation with
mask
takesO(1)
time - Dictionary lookup
cnt[mask]
takesO(1)
average time - Dictionary update
cnt[mask] += 1
takesO(1)
average time - Addition operation for
ans
takesO(1)
time
Since we perform constant-time operations for each of the n
elements, the overall time complexity is O(n)
, where n
is the length of the array nums
.
Space Complexity: O(n)
The space complexity is determined by the Counter
dictionary cnt
which stores the frequency of each unique XOR prefix value. In the worst case, all prefix XOR values are distinct, which would require storing n + 1
entries (including the initial {0: 1}
). Therefore, the space complexity is O(n)
, where n
is the length of the array nums
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Forgetting to Initialize the Counter with {0: 1}
The Problem:
A common mistake is initializing an empty counter without the base case {0: 1}
. This causes the algorithm to miss beautiful subarrays that start from index 0.
Why it happens:
When a prefix XOR equals 0, it means the entire subarray from the beginning is beautiful. Without {0: 1}
, we don't count these cases.
Example:
For array [4, 3, 1, 2, 4]
, the prefix XOR at index 3 is 0
(4^3^1^2 = 0). Without the initial {0: 1}
, we'd miss counting the subarray [4, 3, 1, 2]
.
Solution:
# Wrong prefix_xor_count = Counter() # Missing base case # Correct prefix_xor_count = Counter({0: 1}) # Handles subarrays from start
Pitfall 2: Updating Counter Before Adding to Result
The Problem:
Incrementing prefix_xor_count[current_xor]
before adding its value to beautiful_count
leads to overcounting.
Why it happens: We want to count how many times we've seen this XOR value before the current position, not including the current position itself.
Example:
# Wrong - counts current position in the result for num in nums: current_xor ^= num prefix_xor_count[current_xor] += 1 # Updated first beautiful_count += prefix_xor_count[current_xor] # Now includes current # Correct - only counts previous occurrences for num in nums: current_xor ^= num beautiful_count += prefix_xor_count[current_xor] # Count previous prefix_xor_count[current_xor] += 1 # Then update
Pitfall 3: Misunderstanding XOR Properties
The Problem: Attempting to track individual bit frequencies instead of using XOR, leading to complex and inefficient solutions.
Why it happens: The problem statement talks about bits appearing even/odd times, which might lead to tracking bit counts explicitly.
Wrong approach:
# Overly complex - tracking each bit position
def countBits(subarray):
bit_counts = [0] * 32
for num in subarray:
for bit in range(32):
if num & (1 << bit):
bit_counts[bit] += 1
return all(count % 2 == 0 for count in bit_counts)
Correct approach:
Using XOR naturally handles the even/odd property since a ^ a = 0
for any value a
.
Pitfall 4: Integer Overflow Concerns (Language-Specific)
The Problem: In languages with fixed integer sizes, XOR operations on large numbers might cause unexpected behavior.
Solution: Python handles arbitrary precision integers automatically, but in other languages:
# For other languages, ensure proper data types # C++: use long long or unsigned int # Java: use long if needed
Pitfall 5: Not Understanding Why XOR Works
The Problem: Using the XOR solution without understanding leads to errors when modifying or debugging the code.
Key insight to remember:
- XOR of a number with itself is 0:
a ^ a = 0
- XOR is commutative and associative: order doesn't matter
- If subarray has XOR = 0, every bit appears even times
- The operation in the problem (subtracting 2^k from two numbers) is equivalent to flipping the k-th bit in both numbers
- Beautiful subarray ≡ XOR of all elements = 0 ≡ all bits can be paired and eliminated
Which of the following uses divide and conquer strategy?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Want a Structured Path to Master System Design Too? Don’t Miss This!