LeetCode 1434 - Number of Ways to Wear Different Hats to Each Other
This problem asks us to count how many valid ways exist to assign hats to people under two constraints: 1. Every person
Difficulty: 🔴 Hard
Topics: Array, Dynamic Programming, Bit Manipulation, Bitmask
Solution
LeetCode 1434 - Number of Ways to Wear Different Hats to Each Other
Problem Understanding
This problem asks us to count how many valid ways exist to assign hats to people under two constraints:
- Every person must receive exactly one hat they like.
- No two people may wear the same hat.
The input is a 2D array called hats.
hats[i]contains all hat numbers preferred by personi- Hat numbers range from
1to40 - There are at most
10people
The goal is to compute the total number of distinct valid assignments and return the answer modulo 10^9 + 7.
A valid assignment means:
- Every person receives exactly one hat
- The assigned hat belongs to that person's preference list
- Every assigned hat is unique
For example:
hats = [[3,5,1],[3,5]]
Person 0 likes hats 3, 5, and 1.
Person 1 likes hats 3 and 5.
Valid assignments are:
(3,5)
(5,3)
(1,3)
(1,5)
So the answer is 4.
The constraints are extremely important:
n <= 10- Hat labels are limited to
40
At first glance, this looks like a bipartite matching counting problem. The important observation is that the number of people is very small, while the number of hat types is fixed at 40.
This strongly suggests using a bitmask to represent which people have already received hats.
The main edge cases include:
- Multiple people liking the same small set of hats
- A person having only one possible hat
- Situations where no valid assignment exists
- Large overlap between preferences
- Maximum-size preference lists
A naive brute-force solution becomes infeasible because the number of assignments grows exponentially.
Approaches
Brute Force Approach
The brute-force solution tries every possible assignment of hats to people.
One way to implement this is:
- Process people one by one
- For each person, try every hat they like
- Skip hats already used
- Recursively continue until all people are assigned
This guarantees correctness because every valid assignment is explored exactly once.
However, the complexity becomes extremely large.
If each person likes many hats, we may explore roughly:
40 * 39 * 38 * ...
possibilities.
In the worst case, this becomes far too expensive.
Even though n <= 10, the branching factor is huge.
Key Insight
The critical observation is that there are only 10 people, which means we can represent assignment states using a bitmask of size 2^10.
Instead of assigning hats to people, we reverse the thinking:
- Iterate through hats from
1to40 - For each hat, decide whether to give it to one eligible person
- Track which people already have hats using a bitmask
This transformation is powerful because:
- The number of hats is fixed at
40 - The number of people states is only
2^10 = 1024
This allows dynamic programming over:
(current_hat, assigned_people_mask)
The DP state represents:
How many ways can we assign hats starting from this hat number, given the current set of people already assigned hats?
This drastically reduces the search space.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(40^n) | O(n) | Tries all assignments recursively |
| Optimal | O(40 × 2^n × n) | O(2^n) | Bitmask DP over hats |
Algorithm Walkthrough
Step 1: Build Reverse Mapping
Instead of storing:
person -> hats
we build:
hat -> people
This allows us to efficiently determine which people can wear a given hat.
For example:
hats = [[3,4],[4,5],[5]]
becomes:
3 -> [0]
4 -> [0,1]
5 -> [1,2]
This is useful because we process hats sequentially.
Step 2: Define the Bitmask
We use a bitmask to represent which people already have hats.
For n = 3:
mask = 101
means:
- Person
0has a hat - Person
1does not - Person
2has a hat
The final target state is:
(1 << n) - 1
which means all people have hats assigned.
Step 3: Define DP State
We define:
dp[mask]
as:
Number of ways to reach this assignment state after processing some hats.
Initially:
dp[0] = 1
because there is one way to assign no hats.
Step 4: Process Hats One by One
For every hat from 1 to 40:
- Either skip the hat
- Or assign it to one eligible unassigned person
We create a new DP array for each hat.
This prevents reusing the same hat multiple times in a single iteration.
Step 5: Try Assigning Current Hat
For each current mask:
-
Check every person who likes this hat
-
If that person is unassigned in the current mask:
-
Create a new mask with that person assigned
-
Add the current count into the new state
The transition looks like:
new_mask = mask | (1 << person)
Step 6: Return Final State
After processing all hats:
dp[all_people_assigned]
contains the number of valid assignments.
Return this value modulo 10^9 + 7.
Why it works
The algorithm works because every hat is processed exactly once, and every valid assignment corresponds to a unique sequence of DP transitions.
The bitmask guarantees that:
- No person receives more than one hat
- No hat is reused
Since every possible legal assignment is explored through DP transitions, the final count is correct.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def numberWays(self, hats: List[List[int]]) -> int:
MOD = 10**9 + 7
n = len(hats)
hat_to_people = defaultdict(list)
for person, preferred_hats in enumerate(hats):
for hat in preferred_hats:
hat_to_people[hat].append(person)
total_masks = 1 << n
dp = [0] * total_masks
dp[0] = 1
for hat in range(1, 41):
next_dp = dp[:]
for mask in range(total_masks):
if dp[mask] == 0:
continue
for person in hat_to_people[hat]:
if mask & (1 << person):
continue
new_mask = mask | (1 << person)
next_dp[new_mask] = (
next_dp[new_mask] + dp[mask]
) % MOD
dp = next_dp
return dp[total_masks - 1]
The implementation begins by constructing a reverse mapping from hats to people. This allows efficient access to all people who can wear a particular hat.
The DP array has size 2^n, where each index represents a bitmask of assigned people.
dp[mask] stores the number of ways to achieve that assignment state.
For every hat from 1 to 40, we create a copy called next_dp.
The copy is important because it ensures each hat is used at most once during transitions.
For every mask:
- If the state count is zero, we skip it
- Otherwise, we try assigning the current hat to every eligible unassigned person
The new assignment state is created with a bitwise OR operation.
Finally, after processing all hats, the answer is stored in the mask where all bits are set.
Go Solution
package main
func numberWays(hats [][]int) int {
const MOD = 1000000007
n := len(hats)
hatToPeople := make([][]int, 41)
for person, preferredHats := range hats {
for _, hat := range preferredHats {
hatToPeople[hat] = append(hatToPeople[hat], person)
}
}
totalMasks := 1 << n
dp := make([]int, totalMasks)
dp[0] = 1
for hat := 1; hat <= 40; hat++ {
nextDP := make([]int, totalMasks)
copy(nextDP, dp)
for mask := 0; mask < totalMasks; mask++ {
if dp[mask] == 0 {
continue
}
for _, person := range hatToPeople[hat] {
if (mask & (1 << person)) != 0 {
continue
}
newMask := mask | (1 << person)
nextDP[newMask] =
(nextDP[newMask] + dp[mask]) % MOD
}
}
dp = nextDP
}
return dp[totalMasks-1]
}
The Go implementation follows the same logic as the Python version.
A slice of slices is used for the reverse mapping:
hatToPeople := make([][]int, 41)
Bit operations work naturally with integers in Go.
The copy function is used to duplicate the DP array for each hat iteration.
Modulo arithmetic is performed after every update to prevent overflow.
Worked Examples
Example 1
hats = [[3,4],[4,5],[5]]
Reverse Mapping
| Hat | People |
|---|---|
| 3 | [0] |
| 4 | [0,1] |
| 5 | [1,2] |
Initial DP
| Mask | Meaning | Ways |
|---|---|---|
| 000 | nobody assigned | 1 |
Process Hat 3
Person 0 can wear it.
| Old Mask | New Mask | Update |
|---|---|---|
| 000 | 001 | +1 |
DP becomes:
| Mask | Ways |
|---|---|
| 000 | 1 |
| 001 | 1 |
Process Hat 4
Hat 4 can go to persons 0 or 1.
From mask 000:
- assign to
0->001 - assign to
1->010
From mask 001:
- person
0already assigned - assign to
1->011
DP now contains:
| Mask | Ways |
|---|---|
| 000 | 1 |
| 001 | 2 |
| 010 | 1 |
| 011 | 1 |
Process Hat 5
Hat 5 can go to persons 1 or 2.
Eventually we reach:
111 -> 1
Answer:
1
Example 2
hats = [[3,5,1],[3,5]]
Reverse Mapping
| Hat | People |
|---|---|
| 1 | [0] |
| 3 | [0,1] |
| 5 | [0,1] |
Valid Assignments
| Person 0 | Person 1 |
|---|---|
| 3 | 5 |
| 5 | 3 |
| 1 | 3 |
| 1 | 5 |
Answer:
4
Example 3
hats = [[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]
All four people can wear any of the four hats.
This becomes counting permutations of four distinct hats among four people.
Total:
4! = 24
The DP correctly accumulates all permutations.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(40 × 2^n × n) | For every hat, iterate through every mask and candidate person |
| Space | O(2^n) | DP table stores one value per mask |
The number of masks is at most:
2^10 = 1024
For each of the 40 hats, we process all masks and potentially all people.
This is extremely manageable within the constraints.
Test Cases
from typing import List
class Solution:
def numberWays(self, hats: List[List[int]]) -> int:
MOD = 10**9 + 7
n = len(hats)
hat_to_people = [[] for _ in range(41)]
for person, preferred in enumerate(hats):
for hat in preferred:
hat_to_people[hat].append(person)
total_masks = 1 << n
dp = [0] * total_masks
dp[0] = 1
for hat in range(1, 41):
next_dp = dp[:]
for mask in range(total_masks):
if dp[mask] == 0:
continue
for person in hat_to_people[hat]:
if mask & (1 << person):
continue
new_mask = mask | (1 << person)
next_dp[new_mask] = (
next_dp[new_mask] + dp[mask]
) % MOD
dp = next_dp
return dp[total_masks - 1]
sol = Solution()
assert sol.numberWays([[3,4],[4,5],[5]]) == 1
# Basic valid chain assignment
assert sol.numberWays([[3,5,1],[3,5]]) == 4
# Multiple valid assignments
assert sol.numberWays([[1,2,3,4],[1,2,3,4],[1,2,3,4],[1,2,3,4]]) == 24
# Full permutation count
assert sol.numberWays([[1],[1]]) == 0
# Impossible because both want same single hat
assert sol.numberWays([[1],[2]]) == 1
# Simple independent assignment
assert sol.numberWays([[1,2],[2,3],[1,3]]) == 2
# Small overlapping preferences
assert sol.numberWays([[1]]) == 1
# Single person single hat
assert sol.numberWays([[1,2,3]]) == 3
# Single person many choices
assert sol.numberWays([[1,2],[1,2]]) == 2
# Two-way swapping case
assert sol.numberWays([[1,2],[1,2],[1,2]]) == 0
# Not enough unique hats
assert sol.numberWays([[1,2,3],[1,2,3],[1,2,3]]) == 6
# Permutations of 3 hats among 3 people
Test Summary
| Test | Why |
|---|---|
[[3,4],[4,5],[5]] |
Validates basic sequential assignment |
[[3,5,1],[3,5]] |
Tests multiple valid combinations |
[[1,2,3,4], ...] |
Tests permutation-heavy case |
[[1],[1]] |
Tests impossible assignment |
[[1],[2]] |
Tests simplest valid assignment |
[[1,2],[2,3],[1,3]] |
Tests overlapping preferences |
[[1]] |
Smallest possible input |
[[1,2,3]] |
Single person with many hats |
[[1,2],[1,2]] |
Tests symmetric assignments |
[[1,2],[1,2],[1,2]] |
Tests insufficient unique hats |
[[1,2,3],[1,2,3],[1,2,3]] |
Tests factorial-style counting |
Edge Cases
Multiple People Want the Same Single Hat
Consider:
[[1],[1]]
Both people only like hat 1.
Since hats must be unique, only one person can receive the hat.
No valid assignment exists.
A naive implementation may accidentally count invalid duplicate assignments. The bitmask DP prevents this because once a hat is assigned, the transition only updates one new state.
A Person Has Many Choices
Consider:
[[1,2,3,4,5]]
There is only one person, but many acceptable hats.
The algorithm correctly counts each hat as a separate valid assignment because every transition from the empty mask produces a unique final state.
Not Enough Unique Hats
Consider:
[[1,2],[1,2],[1,2]]
There are three people but only two distinct hats.
Even though everyone has preferences, it is impossible to assign unique hats to all people.
The DP naturally handles this because the final full-assignment mask is never reached.
Large Preference Overlap
Consider:
[[1,2,3],[1,2,3],[1,2,3]]
Every person likes the same hats.
This creates many overlapping recursive possibilities.
A brute-force DFS would repeatedly recompute similar states.
The DP avoids recomputation because every assignment state is represented once by its bitmask.