3141. Maximum Hamming Distances π
Problem Description
You are given an array nums
where each element satisfies 0 <= nums[i] < 2^m
for a given integer m
. Your task is to find the maximum Hamming distance for each element in the array.
For each element nums[i]
, you need to find the maximum Hamming distance between nums[i]
and any other element nums[j]
in the array (where i β j
).
The Hamming distance between two binary integers is the number of bit positions where the two numbers differ. When comparing, both numbers should be represented with the same number of bits (add leading zeros if necessary).
The output should be an array answer
of the same length as nums
, where answer[i]
represents the maximum Hamming distance between nums[i]
and any other element in the array.
For example, if we have two numbers and their binary representations differ in 3 positions, their Hamming distance is 3. Since each number in the array is less than 2^m
, they can all be represented using exactly m
bits.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: We can model this problem as a graph where each number (from 0 to 2^m - 1) is a node, and edges connect numbers that differ by exactly one bit (Hamming distance of 1).
Is it a tree?
- No: The graph is not a tree. Numbers can be connected in cycles (e.g., flipping different bits can lead back to the same number through different paths).
Is the problem related to directed acyclic graphs?
- No: The graph is undirected (flipping a bit is reversible) and contains cycles.
Is the problem related to shortest paths?
- Yes: We need to find the shortest path from each number's complement to any number in the input array. The maximum Hamming distance equals m minus this shortest path.
Is the graph Weighted?
- No: Each edge has the same weight (1), representing a single bit flip.
BFS
- Yes: Since we have an unweighted graph and need to find shortest paths, BFS is the appropriate algorithm.
Conclusion: The flowchart leads us to use BFS. The solution treats the problem as finding shortest paths in an unweighted graph where:
- Nodes are all possible m-bit numbers
- Edges connect numbers differing by exactly one bit
- We use BFS starting from the input numbers to find distances to all reachable numbers
- The maximum Hamming distance for each number is m minus the distance to its complement
Intuition
The key insight is to think about the problem in reverse. Instead of directly finding the maximum Hamming distance for each number, we can leverage a clever observation: the number with maximum Hamming distance from x
is its bitwise complement (all bits flipped).
For example, if we have x = 101
in binary, its complement is 010
, and they differ in all 3 bit positions, giving the maximum possible Hamming distance of 3.
However, the complement might not exist in our input array. So we need to find the number in the array that is "closest" to the complement - this gives us the minimum distance from the complement to any array element. The maximum Hamming distance we seek is then m
minus this minimum distance.
Why does this work? Consider any number y
in the array:
- The Hamming distance between
x
andy
equals the number of differing bits - The complement of
x
(let's call itx'
) differs fromx
in allm
bits - If
x'
has distanced
toy
, thenx
has distancem - d
toy
To find the minimum distance from each complement to array elements, we use BFS starting from all array elements simultaneously. We explore the "graph" where each number is connected to numbers that differ by exactly one bit. The BFS naturally finds shortest paths (minimum Hamming distances) from our starting points (array elements) to all reachable numbers.
The algorithm fills in distances layer by layer:
- Layer 0: The array elements themselves (distance 0)
- Layer 1: Numbers one bit flip away (distance 1)
- Layer 2: Numbers two bit flips away (distance 2)
- And so on...
Once we have these distances, for each element x
, we look up the distance to its complement x ^ ((1 << m) - 1)
and subtract from m
to get the maximum Hamming distance.
Learn more about Breadth-First Search patterns.
Solution Approach
The implementation uses BFS to find minimum distances from array elements to all possible m
-bit numbers, then uses these distances to compute maximum Hamming distances.
Step 1: Initialize Distance Array
dist = [-1] * (1 << m) for x in nums: dist[x] = 0
We create a distance array of size 2^m
to store the minimum distance from each number to any element in nums
. Initially set all distances to -1
(unvisited), then mark all numbers in nums
with distance 0
.
Step 2: BFS Initialization
q = nums k = 1
Start BFS with all numbers from nums
as the initial layer (distance 0). Variable k
tracks the current distance level.
Step 3: BFS Exploration
while q:
t = []
for x in q:
for i in range(m):
y = x ^ (1 << i)
if dist[y] == -1:
t.append(y)
dist[y] = k
q = t
k += 1
For each number x
in the current layer:
- Try flipping each of its
m
bits usingx ^ (1 << i)
- If the resulting number
y
hasn't been visited (dist[y] == -1
), mark it with distancek
and add to the next layer - Move to the next layer and increment the distance
The XOR operation x ^ (1 << i)
flips the i
-th bit of x
. For example, if x = 5 (101)
and i = 1
, then 1 << 1 = 010
, and 101 ^ 010 = 111 = 7
.
Step 4: Calculate Maximum Hamming Distances
return [m - dist[x ^ ((1 << m) - 1)] for x in nums]
For each element x
in nums
:
- Calculate its complement:
x ^ ((1 << m) - 1)
where(1 << m) - 1
creates a mask of all 1s - Look up the minimum distance to this complement:
dist[x ^ ((1 << m) - 1)]
- The maximum Hamming distance is
m
minus this minimum distance
Time Complexity: O(2^m * m)
- We potentially visit all 2^m
numbers, and for each, we check m
bit flips.
Space Complexity: O(2^m)
- For the distance array storing distances to all possible m
-bit numbers.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [1, 4, 5]
and m = 3
(all numbers can be represented with 3 bits).
Step 1: Binary Representation
1 = 001
4 = 100
5 = 101
Step 2: Initialize Distance Array
We create a distance array of size 2^3 = 8
:
Index: 0 1 2 3 4 5 6 7 Value: 000 001 010 011 100 101 110 111 dist: [-1, 0, -1, -1, 0, 0, -1, -1]
Numbers 1, 4, and 5 (from our input) get distance 0.
Step 3: BFS Layer by Layer
Layer 0 (distance 0): Start with q = [1, 4, 5]
Processing Layer 0 to find Layer 1:
- From
1 (001)
: Flip each bit- Flip bit 0:
001 ^ 001 = 000
β Add to next layer,dist[0] = 1
- Flip bit 1:
001 ^ 010 = 011
β Add to next layer,dist[3] = 1
- Flip bit 2:
001 ^ 100 = 101
β Already visited (dist[5] = 0)
- Flip bit 0:
- From
4 (100)
: Flip each bit- Flip bit 0:
100 ^ 001 = 101
β Already visited - Flip bit 1:
100 ^ 010 = 110
β Add to next layer,dist[6] = 1
- Flip bit 2:
100 ^ 100 = 000
β Already visited (dist[0] = 1)
- Flip bit 0:
- From
5 (101)
: Flip each bit- Flip bit 0:
101 ^ 001 = 100
β Already visited - Flip bit 1:
101 ^ 010 = 111
β Add to next layer,dist[7] = 1
- Flip bit 2:
101 ^ 100 = 001
β Already visited
- Flip bit 0:
Layer 1 (distance 1): q = [0, 3, 6, 7]
dist: [1, 0, -1, 1, 0, 0, 1, 1]
Processing Layer 1 to find Layer 2:
- From
0 (000)
: Only unvisited neighbor is010
, sodist[2] = 2
- From
3 (011)
:010
is already being set to 2 - From
6 (110)
:010
is already being set to 2 - From
7 (111)
:011
is already visited
Layer 2 (distance 2): q = [2]
Final dist: [1, 0, 2, 1, 0, 0, 1, 1]
Step 4: Calculate Maximum Hamming Distances
For each element, find its complement and look up the distance:
-
For
1 (001)
:- Complement:
001 ^ 111 = 110
(which is 6) dist[6] = 1
- Maximum Hamming distance:
3 - 1 = 2
- Complement:
-
For
4 (100)
:- Complement:
100 ^ 111 = 011
(which is 3) dist[3] = 1
- Maximum Hamming distance:
3 - 1 = 2
- Complement:
-
For
5 (101)
:- Complement:
101 ^ 111 = 010
(which is 2) dist[2] = 2
- Maximum Hamming distance:
3 - 2 = 1
- Complement:
Verification:
- Max distance from
1 (001)
: Compare with4 (100)
β 2 differing bits β - Max distance from
4 (100)
: Compare with1 (001)
β 2 differing bits β - Max distance from
5 (101)
: Compare with both1 (001)
and4 (100)
β 1 differing bit each β
Final Answer: [2, 2, 1]
Solution Implementation
1class Solution:
2 def maxHammingDistances(self, nums: List[int], m: int) -> List[int]:
3 # Initialize distance array for all possible m-bit numbers
4 # -1 indicates the number hasn't been visited yet
5 distances = [-1] * (1 << m)
6
7 # Mark all numbers in nums as having distance 0 (starting points)
8 for num in nums:
9 distances[num] = 0
10
11 # Initialize BFS queue with all numbers from nums
12 queue = nums
13 current_distance = 1
14
15 # BFS to find minimum distance from each number to any number in nums
16 while queue:
17 next_queue = []
18
19 # Process all numbers at current distance level
20 for current_num in queue:
21 # Try flipping each bit position
22 for bit_position in range(m):
23 # Generate neighbor by flipping the bit at bit_position
24 neighbor = current_num ^ (1 << bit_position)
25
26 # If this neighbor hasn't been visited yet
27 if distances[neighbor] == -1:
28 next_queue.append(neighbor)
29 distances[neighbor] = current_distance
30
31 # Move to next distance level
32 queue = next_queue
33 current_distance += 1
34
35 # Calculate maximum Hamming distance for each number in nums
36 # The maximum distance is m minus the minimum distance to the complement
37 all_bits_mask = (1 << m) - 1
38 result = [m - distances[num ^ all_bits_mask] for num in nums]
39
40 return result
41
1class Solution {
2 public int[] maxHammingDistances(int[] nums, int m) {
3 // Create an array to store the minimum distance from each number to any number in nums
4 // The array size is 2^m to cover all possible m-bit numbers
5 int[] minDistanceFromNums = new int[1 << m];
6 Arrays.fill(minDistanceFromNums, -1); // Initialize all distances as unvisited (-1)
7
8 // BFS queue to explore all numbers by flipping bits
9 Deque<Integer> bfsQueue = new ArrayDeque<>();
10
11 // Initialize: all numbers in nums have distance 0 from themselves
12 for (int num : nums) {
13 minDistanceFromNums[num] = 0;
14 bfsQueue.offer(num);
15 }
16
17 // BFS to find minimum distance from each possible m-bit number to the nearest number in nums
18 int currentDistance = 1;
19 while (!bfsQueue.isEmpty()) {
20 int levelSize = bfsQueue.size();
21
22 // Process all numbers at the current distance level
23 for (int i = 0; i < levelSize; i++) {
24 int currentNum = bfsQueue.poll();
25
26 // Try flipping each bit to generate neighbors (Hamming distance = 1)
27 for (int bitPosition = 0; bitPosition < m; bitPosition++) {
28 int neighbor = currentNum ^ (1 << bitPosition); // Flip the bit at bitPosition
29
30 // If this neighbor hasn't been visited yet
31 if (minDistanceFromNums[neighbor] == -1) {
32 bfsQueue.offer(neighbor);
33 minDistanceFromNums[neighbor] = currentDistance;
34 }
35 }
36 }
37 currentDistance++;
38 }
39
40 // For each number in nums, find its maximum Hamming distance to any number in nums
41 // The maximum distance is achieved with its bitwise complement
42 int allBitsMask = (1 << m) - 1; // Creates a mask with all m bits set to 1
43 for (int i = 0; i < nums.length; i++) {
44 int complement = nums[i] ^ allBitsMask; // Bitwise complement within m bits
45 // Maximum Hamming distance = m - (minimum distance from complement to any number in nums)
46 nums[i] = m - minDistanceFromNums[complement];
47 }
48
49 return nums;
50 }
51}
52
1class Solution {
2public:
3 vector<int> maxHammingDistances(vector<int>& nums, int m) {
4 // Array to store minimum distance from any number in nums to each possible value
5 // Initialize with -1 to indicate unvisited
6 int distance[1 << m];
7 memset(distance, -1, sizeof(distance));
8
9 // BFS queue to explore all possible values
10 queue<int> bfsQueue;
11
12 // Initialize: mark all numbers in nums as having distance 0 to themselves
13 for (int num : nums) {
14 distance[num] = 0;
15 bfsQueue.push(num);
16 }
17
18 // BFS to find minimum distance from any number in nums to all possible m-bit values
19 for (int currentDistance = 1; !bfsQueue.empty(); ++currentDistance) {
20 // Process all nodes at current distance level
21 int levelSize = bfsQueue.size();
22 for (int i = 0; i < levelSize; ++i) {
23 int currentValue = bfsQueue.front();
24 bfsQueue.pop();
25
26 // Try flipping each bit to generate neighbors (Hamming distance 1)
27 for (int bitPosition = 0; bitPosition < m; ++bitPosition) {
28 int neighbor = currentValue ^ (1 << bitPosition);
29
30 // If neighbor hasn't been visited, mark its distance and add to queue
31 if (distance[neighbor] == -1) {
32 distance[neighbor] = currentDistance;
33 bfsQueue.push(neighbor);
34 }
35 }
36 }
37 }
38
39 // Calculate maximum Hamming distance for each number in nums
40 // Maximum Hamming distance from x is m minus the minimum distance to its complement
41 for (int& num : nums) {
42 int complement = num ^ ((1 << m) - 1); // Flip all m bits to get complement
43 num = m - distance[complement];
44 }
45
46 return nums;
47 }
48};
49
1/**
2 * Calculates the maximum Hamming distance for each number in the array
3 * from any other number in the array within the m-bit space.
4 *
5 * @param nums - Array of integers to process
6 * @param m - Number of bits to consider (creates a space of 2^m possible values)
7 * @returns Array where each element represents the maximum Hamming distance
8 * for the corresponding element in the input array
9 */
10function maxHammingDistances(nums: number[], m: number): number[] {
11 // Initialize distance array for all possible m-bit numbers
12 // -1 indicates unvisited, will store minimum distance from any number in nums
13 const distances: number[] = Array.from({ length: 1 << m }, () => -1);
14
15 // Queue for BFS traversal
16 let queue: number[] = [];
17
18 // Mark all numbers in nums as starting points with distance 0
19 for (const num of nums) {
20 distances[num] = 0;
21 queue.push(num);
22 }
23
24 // BFS to find minimum distances to all reachable numbers
25 // k represents the current distance level
26 for (let currentDistance = 1; queue.length > 0; currentDistance++) {
27 const nextLevelQueue: number[] = [];
28
29 // Process all numbers at current distance level
30 for (const currentNum of queue) {
31 // Try flipping each bit position
32 for (let bitPosition = 0; bitPosition < m; bitPosition++) {
33 // Generate neighbor by flipping the bit at bitPosition
34 const neighbor = currentNum ^ (1 << bitPosition);
35
36 // If neighbor hasn't been visited, mark it with current distance
37 if (distances[neighbor] === -1) {
38 distances[neighbor] = currentDistance;
39 nextLevelQueue.push(neighbor);
40 }
41 }
42 }
43
44 // Move to next level
45 queue = nextLevelQueue;
46 }
47
48 // Calculate maximum Hamming distance for each number in nums
49 // The maximum distance is m minus the minimum distance to the complement
50 const maxBitMask = (1 << m) - 1;
51 for (let i = 0; i < nums.length; i++) {
52 const complement = nums[i] ^ maxBitMask;
53 nums[i] = m - distances[complement];
54 }
55
56 return nums;
57}
58
Time and Space Complexity
The time complexity is O(2^m Γ m)
and the space complexity is O(2^m)
.
Time Complexity Analysis:
- The algorithm initializes a distance array
dist
of size2^m
with timeO(2^m)
- It then performs a BFS traversal starting from all numbers in
nums
- In the worst case, the BFS visits all
2^m
possible values - For each value visited, the algorithm checks all
m
bit positions to generate neighbors by flipping each bit (the inner loopfor i in range(m)
) - Each value is processed exactly once due to the
dist[y] == -1
check - Therefore, the total time complexity is
O(2^m Γ m)
Space Complexity Analysis:
- The
dist
array stores distances for all2^m
possible values, requiringO(2^m)
space - The queue
q
can contain at mostO(2^m)
elements across all iterations - The temporary list
t
also has maximum sizeO(2^m)
in the worst case - The overall space complexity is
O(2^m)
Note: The reference answer states time complexity as O(2^m)
, which appears to omit the factor of m
from the inner loop that iterates through all bit positions.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Relationship Between BFS Distance and Hamming Distance
The Problem:
A common misconception is thinking that the BFS distance from a number directly gives us the Hamming distance. The code uses dist[x ^ ((1 << m) - 1)]
to find the minimum BFS distance to the complement, then subtracts from m
. This indirect approach can be confusing.
Why This Happens:
- BFS finds the minimum number of single-bit flips needed to reach any number from the array elements
- The complement of a number has maximum Hamming distance
m
from it - If we can reach the complement in
d
steps, then the closest array element to the complement has Hamming distancem - d
from our original number
Solution: Add clear documentation or consider a more direct approach:
# Direct approach: explicitly calculate Hamming distances
def maxHammingDistances(self, nums: List[int], m: int) -> List[int]:
result = []
for i, num1 in enumerate(nums):
max_distance = 0
for j, num2 in enumerate(nums):
if i != j:
# Direct Hamming distance calculation
hamming_dist = bin(num1 ^ num2).count('1')
max_distance = max(max_distance, hamming_dist)
result.append(max_distance)
return result
Pitfall 2: Incorrect Bit Mask for Complement Calculation
The Problem: Using the wrong expression for creating the all-ones mask. Common mistakes include:
- Using
(1 << m)
instead of(1 << m) - 1
- Using
2^m - 1
(which is XOR in Python, not exponentiation)
Why This Happens:
(1 << m)
gives us100...0
(m zeros) which is2^m
(1 << m) - 1
gives us111...1
(m ones) which is what we need for complement- Python uses
**
for exponentiation, not^
(which is XOR)
Solution: Always verify the mask creation:
# Correct mask generation all_bits_mask = (1 << m) - 1 # Creates m ones: 111...1 # Verify with example: m = 3 # (1 << 3) = 8 = 1000 in binary # (1 << 3) - 1 = 7 = 0111 in binary β # Wrong approaches to avoid: # mask = 1 << m # This gives 1000, not 0111 # mask = 2^m - 1 # This is XOR, not exponentiation
Pitfall 3: Handling Edge Cases with Array Size
The Problem: The algorithm assumes there are at least 2 elements in the array (since we need to compare with "any other element"). If the array has only 1 element, the problem becomes undefined.
Why This Happens:
- The problem statement says to find maximum Hamming distance to "any other element"
- With only one element, there is no "other element" to compare with
Solution: Add validation for edge cases:
def maxHammingDistances(self, nums: List[int], m: int) -> List[int]:
# Handle edge case
if len(nums) == 1:
return [0] # or raise an exception depending on requirements
# Continue with the BFS approach...
distances = [-1] * (1 << m)
# ... rest of the implementation
Pitfall 4: Memory Issues with Large m Values
The Problem:
The algorithm creates an array of size 2^m
. For large values of m
(e.g., m = 30), this requires enormous memory (over 1 billion integers).
Why This Happens:
- The BFS approach explores all possible m-bit numbers
- Storage requirement grows exponentially with m
Solution: For large m values, use the direct O(nΒ²) approach instead:
def maxHammingDistances(self, nums: List[int], m: int) -> List[int]:
# If m is too large, use direct calculation
if m > 20: # Threshold can be adjusted
result = []
for i in range(len(nums)):
max_dist = 0
for j in range(len(nums)):
if i != j:
max_dist = max(max_dist, bin(nums[i] ^ nums[j]).count('1'))
result.append(max_dist)
return result
# Otherwise use the BFS approach for better performance with small m
# ... BFS implementation
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
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!