2172. Maximum AND Sum of Array
Problem Description
You have an integer array nums
with n
elements and a number of slots numSlots
. The constraint guarantees that 2 * numSlots >= n
, meaning you have enough space to place all numbers.
Each slot is numbered from 1
to numSlots
, and each slot can hold at most two numbers from the array.
Your task is to distribute all n
numbers from nums
into these slots to maximize the AND sum.
The AND sum is calculated by:
- Taking each number you've placed
- Performing a bitwise AND operation between that number and its slot number
- Summing all these AND results
For example, if you place numbers [1, 3]
in slot 1
and [4, 6]
in slot 2
:
1 AND 1 = 1
3 AND 1 = 1
4 AND 2 = 0
6 AND 2 = 2
- Total AND sum =
1 + 1 + 0 + 2 = 4
The goal is to find the optimal placement of all numbers that gives you the maximum possible AND sum.
The solution uses dynamic programming with bitmask to track which "positions" (considering each slot has 2 positions) are filled. The state f[i]
represents the maximum AND sum when the positions indicated by bitmask i
are occupied. For each state, it tries removing each number and calculates the best possible sum by considering which slot position that number was in.
Intuition
The key insight is that we need to try different assignments of numbers to slots to find the maximum AND sum. Since each slot can hold up to 2 numbers and we have numSlots
slots, we essentially have 2 * numSlots
positions available.
We can think of this as having 2 * numSlots
distinct positions where we can place our numbers. Position 0
and 1
belong to slot 1
, positions 2
and 3
belong to slot 2
, and so on. For position j
, the corresponding slot number is j // 2 + 1
.
The challenge is exploring all possible placements efficiently. This is where bitmask dynamic programming comes in. We can use a bitmask of length 2 * numSlots
where each bit represents whether a position is occupied or not.
For a given bitmask i
, the number of 1
bits tells us how many numbers from nums
we've placed so far. If we've placed cnt
numbers, they must be the first cnt
numbers from the array (we can process them in order since the final AND sum doesn't depend on which specific number goes where, only on the pairing of numbers with slots).
The dynamic programming approach works backwards: for each state with cnt
numbers placed, we try removing the last placed number (nums[cnt-1]
) from each possible position. If position j
is occupied in the current state (bit j
is set), we can compute what the maximum sum would be if we remove that number from position j
. The AND contribution of that number would be nums[cnt-1] & (j // 2 + 1)
.
By iterating through all valid states (where the number of placed items doesn't exceed n
) and trying all possible last placements, we build up the maximum AND sum for each configuration. The answer is the maximum value among all states in our DP table.
Learn more about Dynamic Programming and Bitmask patterns.
Solution Approach
The solution uses bitmask dynamic programming to explore all possible placements of numbers into slots.
State Representation:
- We create an array
f
of size1 << m
wherem = numSlots * 2
(total positions available) f[i]
stores the maximum AND sum achievable when the positions indicated by bitmaski
are occupied- Each bit in the bitmask represents whether a position is filled (1) or empty (0)
Implementation Steps:
-
Initialize variables:
n = len(nums)
- number of elements to placem = numSlots << 1
- total positions (2 per slot)f = [0] * (1 << m)
- DP array initialized with zeros
-
Iterate through all possible states:
for i in range(1 << m):
Each
i
represents a different configuration of filled positions. -
Count placed numbers:
cnt = i.bit_count() if cnt > n: continue
bit_count()
returns the number of 1s in the bitmask- Skip states where we've placed more numbers than available
-
Try removing each placed number:
for j in range(m): if i >> j & 1:
- Check if position
j
is occupied in statei
- If yes, calculate the maximum sum by considering this as the last placement
- Check if position
-
Update DP value:
f[i] = max(f[i], f[i ^ (1 << j)] + (nums[cnt - 1] & (j // 2 + 1)))
i ^ (1 << j)
gives the previous state with positionj
emptynums[cnt - 1]
is the last number placed (we place numbers in order)j // 2 + 1
converts position index to slot number (positions 0,1 → slot 1, positions 2,3 → slot 2, etc.)- The AND operation
nums[cnt - 1] & (j // 2 + 1)
calculates the contribution
-
Return the maximum:
return max(f)
The answer is the maximum value across all valid states in the DP table.
Time Complexity: O(2^(2*numSlots) * 2*numSlots)
- we iterate through all states and for each state, check all positions.
Space Complexity: O(2^(2*numSlots))
- for storing the DP array.
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 nums = [3, 5]
and numSlots = 2
.
Setup:
- We have 2 numbers to place: [3, 5]
- We have 2 slots (slot 1 and slot 2), each can hold up to 2 numbers
- Total positions available:
m = 2 * 2 = 4
positions- Positions 0,1 belong to slot 1
- Positions 2,3 belong to slot 2
- DP array size:
2^4 = 16
states
Key States Walkthrough:
Initially, f[0000] = 0
(no numbers placed, AND sum = 0)
Placing 1 number (cnt = 1, placing nums[0] = 3):
- State
0001
(position 0 filled):f[0001] = f[0000] + (3 & 1) = 0 + 1 = 1
- State
0010
(position 1 filled):f[0010] = f[0000] + (3 & 1) = 0 + 1 = 1
- State
0100
(position 2 filled):f[0100] = f[0000] + (3 & 2) = 0 + 2 = 2
- State
1000
(position 3 filled):f[1000] = f[0000] + (3 & 2) = 0 + 2 = 2
Placing 2 numbers (cnt = 2, placing nums[1] = 5):
- State
0011
(positions 0,1 filled - both in slot 1):- From
0001
:f[0011] = f[0001] + (5 & 1) = 1 + 1 = 2
- From
0010
:f[0011] = f[0010] + (5 & 1) = 1 + 1 = 2
- From
- State
0101
(positions 0,2 filled - one in each slot):- From
0001
:f[0101] = f[0001] + (5 & 2) = 1 + 0 = 1
- From
0100
:f[0101] = f[0100] + (5 & 1) = 2 + 1 = 3
- Maximum:
f[0101] = 3
- From
- State
0110
(positions 1,2 filled):- From
0010
:f[0110] = f[0010] + (5 & 2) = 1 + 0 = 1
- From
0100
:f[0110] = f[0100] + (5 & 1) = 2 + 1 = 3
- Maximum:
f[0110] = 3
- From
- State
1001
(positions 0,3 filled):- From
0001
:f[1001] = f[0001] + (5 & 2) = 1 + 0 = 1
- From
1000
:f[1001] = f[1000] + (5 & 1) = 2 + 1 = 3
- Maximum:
f[1001] = 3
- From
- State
1100
(positions 2,3 filled - both in slot 2):- From
0100
:f[1100] = f[0100] + (5 & 2) = 2 + 0 = 2
- From
1000
:f[1100] = f[1000] + (5 & 2) = 2 + 0 = 2
- From
Result:
The maximum value in the DP array is 3
, which occurs when:
- We place 3 in slot 2 (3 & 2 = 2)
- We place 5 in slot 1 (5 & 1 = 1)
- Total AND sum = 2 + 1 = 3
This demonstrates how the algorithm explores all possible placements and finds the optimal assignment by building up from smaller states to larger ones.
Solution Implementation
1class Solution:
2 def maximumANDSum(self, nums: List[int], numSlots: int) -> int:
3 """
4 Assigns numbers to slots to maximize the sum of (number AND slot_number).
5 Each slot can hold at most 2 numbers.
6
7 Args:
8 nums: List of integers to assign to slots
9 numSlots: Number of available slots (numbered 1 to numSlots)
10
11 Returns:
12 Maximum possible sum of (number AND slot_number) for all assignments
13 """
14 num_count = len(nums)
15 # Total positions available (2 positions per slot)
16 total_positions = numSlots * 2
17
18 # dp[mask] = maximum AND sum when positions indicated by mask are filled
19 # mask bit i represents whether position i is occupied
20 dp = [0] * (1 << total_positions)
21
22 # Iterate through all possible states (masks)
23 for mask in range(1 << total_positions):
24 # Count how many positions are filled in current mask
25 filled_positions = mask.bit_count()
26
27 # Skip if we're trying to place more numbers than available
28 if filled_positions > num_count:
29 continue
30
31 # Try placing the current number at each possible position
32 for position in range(total_positions):
33 # Check if this position is occupied in current mask
34 if (mask >> position) & 1:
35 # Calculate which slot this position belongs to (1-indexed)
36 slot_number = (position // 2) + 1
37
38 # Previous state without this position filled
39 previous_mask = mask ^ (1 << position)
40
41 # Current number being placed (0-indexed in nums)
42 current_num = nums[filled_positions - 1]
43
44 # Update dp with maximum value
45 dp[mask] = max(
46 dp[mask],
47 dp[previous_mask] + (current_num & slot_number)
48 )
49
50 # Return the maximum value across all valid states
51 return max(dp)
52
1class Solution {
2 public int maximumANDSum(int[] nums, int numSlots) {
3 int numsLength = nums.length;
4 // Each slot can hold at most 2 numbers, so total positions = numSlots * 2
5 int totalPositions = numSlots << 1;
6
7 // dp[mask] represents the maximum AND sum when using the slot positions
8 // indicated by the bitmask
9 int[] dp = new int[1 << totalPositions];
10 int maxAndSum = 0;
11
12 // Iterate through all possible bitmask states
13 for (int bitmask = 0; bitmask < (1 << totalPositions); ++bitmask) {
14 // Count how many positions are used (how many bits are set to 1)
15 int usedPositions = Integer.bitCount(bitmask);
16
17 // Skip if we're trying to use more positions than we have numbers
18 if (usedPositions > numsLength) {
19 continue;
20 }
21
22 // Try removing each used position from current bitmask
23 for (int position = 0; position < totalPositions; ++position) {
24 // Check if this position is used in current bitmask
25 if ((bitmask >> position & 1) == 1) {
26 // Calculate the slot number (1-indexed) for this position
27 // Since each slot has 2 positions, divide by 2 and add 1
28 int slotNumber = position / 2 + 1;
29
30 // Previous state: current bitmask without this position
31 int previousMask = bitmask ^ (1 << position);
32
33 // The number to place is the (usedPositions-1)th number
34 // because we're placing numbers in order
35 int currentNumber = nums[usedPositions - 1];
36
37 // Update dp value: max of current value or
38 // (previous state + AND of current number with slot number)
39 dp[bitmask] = Math.max(dp[bitmask],
40 dp[previousMask] + (currentNumber & slotNumber));
41 }
42 }
43
44 // Track the maximum AND sum found so far
45 maxAndSum = Math.max(maxAndSum, dp[bitmask]);
46 }
47
48 return maxAndSum;
49 }
50}
51
1class Solution {
2public:
3 int maximumANDSum(vector<int>& nums, int numSlots) {
4 int n = nums.size();
5 // Each slot can hold 2 numbers, so total positions = numSlots * 2
6 int totalPositions = numSlots << 1;
7
8 // dp[mask] represents the maximum AND sum for the state represented by mask
9 // where bit i in mask indicates whether position i is occupied
10 int dp[1 << totalPositions];
11 memset(dp, 0, sizeof(dp));
12
13 // Iterate through all possible states (bitmasks)
14 for (int mask = 0; mask < (1 << totalPositions); ++mask) {
15 // Count how many positions are occupied in current state
16 int occupiedCount = __builtin_popcount(mask);
17
18 // Skip if more positions are occupied than available numbers
19 if (occupiedCount > n) {
20 continue;
21 }
22
23 // Try to place the current number at each occupied position
24 for (int position = 0; position < totalPositions; ++position) {
25 // Check if this position is occupied in the current mask
26 if ((mask >> position) & 1) {
27 // Previous state without this position occupied
28 int previousMask = mask ^ (1 << position);
29
30 // Calculate slot number (1-indexed): position 0,1 -> slot 1; position 2,3 -> slot 2, etc.
31 int slotNumber = (position / 2) + 1;
32
33 // Current number being placed (0-indexed in nums array)
34 int currentNumber = nums[occupiedCount - 1];
35
36 // Update dp with maximum value
37 dp[mask] = max(dp[mask],
38 dp[previousMask] + (currentNumber & slotNumber));
39 }
40 }
41 }
42
43 // Find the maximum value among all valid states
44 return *max_element(dp, dp + (1 << totalPositions));
45 }
46};
47
1/**
2 * Finds the maximum sum of bitwise AND operations when placing numbers into slots
3 * Each slot can hold at most 2 numbers, and each number must be placed in exactly one slot
4 * @param nums - Array of numbers to be placed into slots
5 * @param numSlots - Number of available slots (each slot numbered from 1 to numSlots)
6 * @returns Maximum possible sum of (number & slot_number) for all placements
7 */
8function maximumANDSum(nums: number[], numSlots: number): number {
9 const numsCount: number = nums.length;
10 // Total positions available (2 per slot since each slot can hold 2 numbers)
11 const totalPositions: number = numSlots << 1; // numSlots * 2
12
13 // Dynamic programming array where dp[mask] represents the maximum sum
14 // for a given state (mask indicates which positions are occupied)
15 const dp: number[] = new Array(1 << totalPositions).fill(0);
16
17 // Iterate through all possible states (bitmasks)
18 for (let mask = 0; mask < (1 << totalPositions); ++mask) {
19 // Count the number of positions occupied in current state
20 const occupiedPositions: number = mask
21 .toString(2)
22 .split('')
23 .filter((bit: string) => bit === '1').length;
24
25 // Skip if more positions are occupied than numbers available
26 if (occupiedPositions > numsCount) {
27 continue;
28 }
29
30 // Try placing the current number in each available position
31 for (let position = 0; position < totalPositions; ++position) {
32 // Check if this position is occupied in the current mask
33 if (((mask >> position) & 1) === 1) {
34 // Calculate the slot number (1-indexed) for this position
35 // Each slot has 2 positions, so divide by 2 and add 1
36 const slotNumber: number = (position >> 1) + 1;
37
38 // Calculate the value if we place the (occupiedPositions-1)th number here
39 // Compare with previous state (without this position occupied)
40 const previousMask: number = mask ^ (1 << position);
41 const currentValue: number = dp[previousMask] + (nums[occupiedPositions - 1] & slotNumber);
42
43 dp[mask] = Math.max(dp[mask], currentValue);
44 }
45 }
46 }
47
48 // Return the maximum value across all valid states
49 return Math.max(...dp);
50}
51
Time and Space Complexity
Time Complexity: O(2^(2*numSlots) * numSlots)
The algorithm iterates through all possible states represented by a bitmask of size 2 * numSlots
. For each state i
in the range [0, 2^(2*numSlots))
, it performs the following operations:
bit_count()
operation:O(1)
in Python 3.10+- Inner loop iterating through
m = 2 * numSlots
positions:O(numSlots)
- Inside the inner loop: bit operations and array access are
O(1)
Therefore, the total time complexity is O(2^(2*numSlots) * numSlots)
.
Space Complexity: O(2^(2*numSlots))
The algorithm uses:
- Array
f
of size2^(2*numSlots)
to store the DP states:O(2^(2*numSlots))
- A few auxiliary variables (
n
,m
,cnt
,i
,j
):O(1)
The dominant factor is the DP array, resulting in a space complexity of O(2^(2*numSlots))
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Slot Number Calculation
Problem: A common mistake is incorrectly mapping position indices to slot numbers. Since each slot has 2 positions, developers often confuse the zero-based position indexing with one-based slot numbering.
Incorrect Implementation:
# Wrong: forgetting to add 1 for 1-based slot numbering slot_number = position // 2 # This gives 0-based slot index # Wrong: incorrect division for position-to-slot mapping slot_number = position / 2 + 1 # Using float division instead of integer
Correct Implementation:
slot_number = (position // 2) + 1 # Correctly converts to 1-based slot number
Pitfall 2: Misunderstanding Number Placement Order
Problem: The algorithm assumes numbers are placed in the order they appear in the input array. Some developers mistakenly think they can place numbers in any order, leading to incorrect indexing when accessing nums[filled_positions - 1]
.
Why This Matters:
- The DP state only tracks which positions are filled, not which specific numbers are in those positions
- By placing numbers in order, we know that if
k
positions are filled, we've placednums[0]
throughnums[k-1]
- Trying to optimize by reordering numbers would require a completely different state representation
Solution: Always place numbers sequentially from the input array. If you want to explore different orderings, you'd need to modify the entire DP approach to track which specific numbers have been placed.
Pitfall 3: Overflow with Large numSlots
Problem: The space complexity is O(2^(2*numSlots))
, which grows exponentially. For numSlots = 16
, this requires 2^32
array elements, consuming gigabytes of memory.
Symptoms:
- Memory allocation failures
- Program crashes or timeouts
- Python MemoryError exceptions
Solution:
- Check problem constraints carefully - most problems limit
numSlots
to reasonable values (typically ≤ 9) - If larger values are needed, consider alternative approaches like branch-and-bound or greedy heuristics
- Add validation:
if numSlots > 15: # Practical limit for bitmask DP raise ValueError("numSlots too large for bitmask DP approach")
Pitfall 4: Confusion About State Representation
Problem: Misunderstanding what each bit in the mask represents. Some developers think each bit represents a slot (not a position), leading to incorrect state transitions.
Incorrect Mental Model:
- Thinking mask bit
i
represents whether sloti
has any numbers - This would only allow tracking whether slots are used, not how many numbers each contains
Correct Understanding:
- Each slot has exactly 2 positions (even if not fully utilized)
- Positions 0,1 belong to slot 1; positions 2,3 belong to slot 2, etc.
- The mask tracks individual positions, allowing us to know if a slot has 0, 1, or 2 numbers
Debugging Tip: Add helper visualization:
def visualize_mask(mask, total_positions):
positions = []
for i in range(total_positions):
if (mask >> i) & 1:
slot = (i // 2) + 1
pos_in_slot = i % 2
positions.append(f"Slot{slot}_Pos{pos_in_slot}")
return positions
Which data structure is used to implement recursion?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Bitmask and Dynamic Programming Bit manipulation is a crucial aspect of computer programming and one of the most powerful tools for bit manipulation is bitmasks Let's first understand what a bit is A bit is a binary digit It's the smallest piece of data in a computer and can be
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
Want a Structured Path to Master System Design Too? Don’t Miss This!