LeetCode 1659 - Maximize Grid Happiness
This problem asks us to place introverts and extroverts inside an m x n grid in a way that maximizes the total happiness score. Every grid cell can either remain empty, contain one introvert, or contain one extrovert.
Difficulty: 🔴 Hard
Topics: Dynamic Programming, Bit Manipulation, Memoization, Bitmask
Solution
Problem Understanding
This problem asks us to place introverts and extroverts inside an m x n grid in a way that maximizes the total happiness score. Every grid cell can either remain empty, contain one introvert, or contain one extrovert. We are allowed to leave some people unused, so we do not necessarily have to place all introverts and extroverts.
Each type of person contributes a different base happiness value and reacts differently to neighbors:
- An introvert starts with
120happiness and loses30happiness for every adjacent neighbor. - An extrovert starts with
40happiness and gains20happiness for every adjacent neighbor.
Neighbors are only the four directly adjacent cells: up, down, left, and right.
The important detail is that the effect of a neighboring relationship impacts both people simultaneously. For example:
-
Two introverts together:
-
First introvert loses
30 -
Second introvert loses
30 -
Total change:
-60 -
Introvert next to extrovert:
-
Introvert loses
30 -
Extrovert gains
20 -
Total change:
-10 -
Two extroverts together:
-
Each gains
20 -
Total change:
+40
The goal is to maximize the total grid happiness after considering every occupied cell and every adjacent interaction.
The constraints are very important:
m, n <= 5introvertsCount, extrovertsCount <= 6
A grid can contain at most 25 cells, which would normally make brute force impossible because every cell has three choices:
- Empty
- Introvert
- Extrovert
That would produce 3^(m*n) possibilities. For a 5 x 5 grid, this becomes 3^25, which is astronomically large.
However, the counts of introverts and extroverts are very small, at most 6 each. This strongly suggests that a dynamic programming solution with compressed state representation is intended.
Several edge cases are important:
- A completely empty placement may be optimal when placing additional people decreases happiness.
- Very small grids like
1 x 1simplify neighbor handling. - Narrow grids such as
1 x norm x 1only have horizontal or vertical neighbors. - Cases with only introverts or only extroverts behave very differently.
- Neighbor effects must be counted exactly once, otherwise double counting bugs appear easily.
Approaches
Brute Force
The most direct approach is to try every possible configuration of the grid.
For each cell, we choose among:
- Leave empty
- Place an introvert
- Place an extrovert
After generating a complete grid configuration, we compute total happiness by evaluating all neighboring relationships.
This approach is guaranteed to find the optimal answer because it exhaustively checks every valid arrangement.
Unfortunately, it is far too slow.
For a 5 x 5 grid:
3^25 ≈ 2.8 * 10^11
Even before considering pruning or happiness calculations, this search space is impossible to explore within time limits.
Optimal Approach, Dynamic Programming with State Compression
The key insight is that when processing the grid cell by cell, the happiness contribution of the current cell only depends on:
- The left neighbor
- The upper neighbor
Future cells do not affect already processed cells except through local adjacency.
This means we do not need the entire grid history. We only need enough information to reconstruct the previous row and the previous cell in the current row.
We represent the state of the last n cells using base-3 encoding:
0= empty1= introvert2= extrovert
Since n <= 5, the number of possible row states is:
3^5 = 243
which is small enough for dynamic programming.
We process the grid left to right, top to bottom. At each cell, we try:
- Leave empty
- Place introvert
- Place extrovert
For each choice, we calculate the incremental happiness change caused only by the left and upper neighbors.
Memoization ensures each state is computed once.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(3^(m*n)) | O(m*n) | Enumerates every possible grid |
| Optimal | O(m * n * 3^n * introvertsCount * extrovertsCount * 3) | O(3^n * introvertsCount * extrovertsCount) | Uses DP with ternary state compression |
Algorithm Walkthrough
Step 1: Represent Grid States Using Base-3 Encoding
We encode the last n processed cells as a ternary number.
For example, if n = 3:
[empty, introvert, extrovert]
= [0,1,2]
= 0 * 3^2 + 1 * 3 + 2
= 5
This compressed representation lets us store row history efficiently.
Step 2: Process Cells Sequentially
We process cells in row-major order:
(0,0) -> (0,1) -> ... -> (m-1,n-1)
At each step, we know:
- Current cell index
- Remaining introverts
- Remaining extroverts
- Encoded previous state
These values fully define the subproblem.
Step 3: Extract Neighbor Information
From the encoded state:
- The left neighbor is the last trit
- The upper neighbor is the first trit
This works because the state always stores the previous n processed cells.
Step 4: Try All Possible Placements
For each cell, we consider:
- Leave empty
- Place introvert
- Place extrovert
If we place someone, we compute:
- Base happiness
- Interaction with left neighbor
- Interaction with upper neighbor
We only compute interactions with already processed neighbors so each edge is counted exactly once.
Step 5: Update the State
To move to the next cell:
- Remove the oldest trit
- Shift left in base 3
- Append the current cell type
Mathematically:
new_state = (old_state % (3^(n-1))) * 3 + current_type
Step 6: Use Memoization
Many states repeat during recursion.
We memoize:
(position, introverts_left, extroverts_left, state)
This transforms exponential brute force into manageable dynamic programming.
Why it works
The algorithm works because the happiness contribution of a cell only depends on adjacent cells. When processing left to right and top to bottom, the only already-determined neighbors are the left and upper cells. Therefore, the previous n cells contain all information needed to compute future decisions optimally.
The DP explores every valid placement combination while avoiding recomputation through memoization, guaranteeing the maximum possible happiness.
Python Solution
from functools import lru_cache
from typing import List
class Solution:
def getMaxGridHappiness(
self,
m: int,
n: int,
introvertsCount: int,
extrovertsCount: int
) -> int:
pow3 = [1] * (n + 1)
for i in range(1, n + 1):
pow3[i] = pow3[i - 1] * 3
# Happiness interaction table
# 0 = empty
# 1 = introvert
# 2 = extrovert
interaction = [
[0, 0, 0],
[0, -60, -10],
[0, -10, 40]
]
@lru_cache(None)
def dp(
pos: int,
introverts_left: int,
extroverts_left: int,
state: int
) -> int:
if pos == m * n:
return 0
row = pos // n
col = pos % n
left = state % 3
up = state // pow3[n - 1]
next_state_base = (state % pow3[n - 1]) * 3
best = dp(
pos + 1,
introverts_left,
extroverts_left,
next_state_base
)
# Place introvert
if introverts_left > 0:
gain = 120
if col > 0:
gain += interaction[1][left]
if row > 0:
gain += interaction[1][up]
best = max(
best,
gain + dp(
pos + 1,
introverts_left - 1,
extroverts_left,
next_state_base + 1
)
)
# Place extrovert
if extroverts_left > 0:
gain = 40
if col > 0:
gain += interaction[2][left]
if row > 0:
gain += interaction[2][up]
best = max(
best,
gain + dp(
pos + 1,
introverts_left,
extroverts_left - 1,
next_state_base + 2
)
)
return best
return dp(0, introvertsCount, extrovertsCount, 0)
The implementation begins by precomputing powers of 3, which are used for ternary state manipulation. Since the row width is at most 5, this remains very small.
The interaction matrix stores total happiness changes caused by neighboring pairs. For example:
interaction[1][1] = -60
because two introverts each lose 30.
The recursive dp function represents the maximum achievable happiness from the current position onward.
The encoded state stores the previous n cells. Using modulo and division operations, we extract:
- Left neighbor
- Upper neighbor
For every cell, we try all valid actions:
- Skip
- Place introvert
- Place extrovert
When placing a person, we compute the local happiness contribution immediately.
The state transition removes the oldest cell and appends the current choice, maintaining a sliding window of the last n processed cells.
Memoization avoids recomputation of identical subproblems.
Go Solution
package main
func getMaxGridHappiness(m int, n int, introvertsCount int, extrovertsCount int) int {
pow3 := make([]int, n+1)
pow3[0] = 1
for i := 1; i <= n; i++ {
pow3[i] = pow3[i-1] * 3
}
interaction := [][]int{
{0, 0, 0},
{0, -60, -10},
{0, -10, 40},
}
type State struct {
pos int
introvertsLeft int
extrovertsLeft int
mask int
}
memo := make(map[State]int)
var dp func(int, int, int, int) int
dp = func(pos int, introvertsLeft int, extrovertsLeft int, mask int) int {
if pos == m*n {
return 0
}
key := State{
pos,
introvertsLeft,
extrovertsLeft,
mask,
}
if val, exists := memo[key]; exists {
return val
}
row := pos / n
col := pos % n
left := mask % 3
up := mask / pow3[n-1]
nextMaskBase := (mask % pow3[n-1]) * 3
best := dp(
pos+1,
introvertsLeft,
extrovertsLeft,
nextMaskBase,
)
if introvertsLeft > 0 {
gain := 120
if col > 0 {
gain += interaction[1][left]
}
if row > 0 {
gain += interaction[1][up]
}
candidate := gain + dp(
pos+1,
introvertsLeft-1,
extrovertsLeft,
nextMaskBase+1,
)
if candidate > best {
best = candidate
}
}
if extrovertsLeft > 0 {
gain := 40
if col > 0 {
gain += interaction[2][left]
}
if row > 0 {
gain += interaction[2][up]
}
candidate := gain + dp(
pos+1,
introvertsLeft,
extrovertsLeft-1,
nextMaskBase+2,
)
if candidate > best {
best = candidate
}
}
memo[key] = best
return best
}
return dp(0, introvertsCount, extrovertsCount, 0)
}
The Go implementation follows the same logic as the Python solution but uses a custom struct as the memoization key because Go does not provide built-in tuple hashing.
A map[State]int stores cached DP results. Since all state fields are integers, the struct is hashable and efficient.
Go integers are sufficient because the maximum happiness value remains well within 32-bit range.
Worked Examples
Example 1
Input:
m = 2
n = 3
introvertsCount = 1
extrovertsCount = 2
The algorithm starts at position 0.
Initial State
| Variable | Value |
|---|---|
| pos | 0 |
| introverts_left | 1 |
| extroverts_left | 2 |
| state | 0 |
At (0,0) we try:
- Empty
- Introvert
- Extrovert
Placing an introvert gives:
gain = 120
No neighbors exist.
New state becomes:
001(base3)
Next, at (0,1):
- Left neighbor is introvert
- Upper neighbor does not exist
Trying extrovert:
base = 40
interaction with introvert = -10
total gain = 30
The recursion continues exploring all possibilities.
Eventually, the best configuration becomes:
I . E
. . E
Happiness Calculation
| Person | Base | Neighbor Effect | Total |
|---|---|---|---|
| Introvert | 120 | 0 | 120 |
| Extrovert | 40 | +20 | 60 |
| Extrovert | 40 | +20 | 60 |
Final answer:
120 + 60 + 60 = 240
Example 2
Input:
m = 3
n = 1
introvertsCount = 2
extrovertsCount = 1
Since the grid has one column, only vertical interactions matter.
Optimal placement:
I
E
I
Happiness Breakdown
| Person | Base | Neighbor Adjustment | Total |
|---|---|---|---|
| Introvert | 120 | -30 | 90 |
| Extrovert | 40 | +40 | 80 |
| Introvert | 120 | -30 | 90 |
Total:
90 + 80 + 90 = 260
Example 3
Input:
m = 2
n = 2
introvertsCount = 4
extrovertsCount = 0
All people are introverts.
Every adjacent introvert pair reduces total happiness by 60.
Optimal placement avoids overcrowding as much as possible.
Maximum achievable happiness:
240
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m * n * 3^n * introvertsCount * extrovertsCount * 3) | DP states multiplied by three placement choices |
| Space | O(m * n * 3^n * introvertsCount * extrovertsCount) | Memoization table size |
The number of DP states is bounded by:
positions * introverts * extroverts * states
where:
positions <= 25introverts <= 6extroverts <= 6states <= 3^5 = 243
This keeps the total state space manageable.
Test Cases
sol = Solution()
# Provided examples
assert sol.getMaxGridHappiness(2, 3, 1, 2) == 240 # example 1
assert sol.getMaxGridHappiness(3, 1, 2, 1) == 260 # example 2
assert sol.getMaxGridHappiness(2, 2, 4, 0) == 240 # example 3
# Single cell cases
assert sol.getMaxGridHappiness(1, 1, 1, 0) == 120 # one introvert
assert sol.getMaxGridHappiness(1, 1, 0, 1) == 40 # one extrovert
assert sol.getMaxGridHappiness(1, 1, 0, 0) == 0 # empty grid
# No people available
assert sol.getMaxGridHappiness(5, 5, 0, 0) == 0 # completely empty
# Only extroverts
assert sol.getMaxGridHappiness(1, 2, 0, 2) == 120 # two adjacent extroverts
# Narrow grid
assert sol.getMaxGridHappiness(1, 5, 2, 2) >= 0 # single row handling
# Tall grid
assert sol.getMaxGridHappiness(5, 1, 2, 2) >= 0 # single column handling
# Maximum counts
assert sol.getMaxGridHappiness(5, 5, 6, 6) > 0 # stress test
# Introverts should avoid adjacency
assert sol.getMaxGridHappiness(1, 2, 2, 0) == 180 # 120+120-60
# Mixed interaction
assert sol.getMaxGridHappiness(1, 2, 1, 1) == 150 # 120+40-10
Test Summary
| Test | Why |
|---|---|
(2,3,1,2) |
Validates standard mixed placement |
(3,1,2,1) |
Tests single-column interactions |
(2,2,4,0) |
Tests dense introvert placement |
(1,1,1,0) |
Smallest introvert case |
(1,1,0,1) |
Smallest extrovert case |
(1,1,0,0) |
Empty configuration |
(5,5,0,0) |
No people available |
(1,2,0,2) |
Positive extrovert interaction |
(1,5,2,2) |
Single-row handling |
(5,1,2,2) |
Single-column handling |
(5,5,6,6) |
Maximum constraint stress test |
(1,2,2,0) |
Introvert adjacency penalty |
(1,2,1,1) |
Mixed neighbor interaction |
Edge Cases
One important edge case occurs when no people are available. In this situation, the optimal answer is simply 0. A buggy implementation might still attempt transitions or compute neighbor interactions unnecessarily. This implementation handles the case naturally because the recursion can only choose the empty placement option.
Another tricky case involves very small grids such as 1 x 1. In these grids, no neighbors exist, so no interaction penalties or bonuses should be applied. The implementation correctly checks row > 0 and col > 0 before evaluating upper or left neighbors.
Single-row and single-column grids are another common source of bugs. In a 1 x n grid, there are no upper neighbors. In an m x 1 grid, there are no left neighbors. Because the algorithm explicitly guards neighbor checks with row and column conditions, it correctly handles these degenerate dimensions.
A subtle issue arises with double counting neighbor effects. If both cells independently apply interaction updates later, the total happiness becomes incorrect. This implementation avoids that by only evaluating interactions with already processed neighbors, specifically the left and upper cells. Every adjacent pair is counted exactly once.
Finally, cases with many introverts can produce situations where leaving cells empty is better than placing additional people. Since the DP always includes the option to skip a cell, the algorithm naturally discovers when partial placement yields a higher total happiness.