1434. Number of Ways to Wear Different Hats to Each Other
Problem Description
You have n
people and 40 types of hats (labeled from 1 to 40). Each person has a list of hats they prefer to wear.
Given a 2D array hats
where hats[i]
contains all the hat types that person i
likes, you need to find the number of ways to assign hats to all n
people such that:
- Each person wears exactly one hat
- Each person wears a hat they prefer (from their list)
- No two people wear the same hat
Since the answer can be very large, return the result modulo 10^9 + 7
.
For example, if person 0 likes hats [1, 2] and person 1 likes hats [2, 3], there are 2 valid ways:
- Person 0 wears hat 1, person 1 wears hat 2
- Person 0 wears hat 2, person 1 wears hat 3
The key constraint is that all n
people must wear different hats from each other, and each person can only wear a hat from their preference list.
Intuition
The first observation is that we have at most 10 people but up to 40 hats. This suggests we should think about the problem from the perspective of hats rather than people, since there are fewer people to track.
Since n ≤ 10
, we can use a bitmask to represent which people have already been assigned hats. For example, if we have 3 people and persons 0 and 2 have been assigned hats, we can represent this state as 101
in binary (or 5 in decimal).
The key insight is to process hats one by one and track which combinations of people have been assigned hats so far. Instead of asking "which hat should person i wear?", we ask "for hat i, which person (if any) should wear it?"
We can build our solution incrementally:
- Start with hat 1, then hat 2, and so on
- For each hat
i
, we have two choices:- Don't assign it to anyone (skip it)
- Assign it to one of the people who likes this hat (if they haven't been assigned a hat yet)
This leads to dynamic programming where f[i][j]
represents the number of ways to assign the first i
hats to achieve state j
, where j
is a bitmask indicating which people have received hats.
The state transition works as follows:
- If we skip hat
i
, thenf[i][j]
includes all ways fromf[i-1][j]
- If we assign hat
i
to personk
(who likes it and is in statej
), then we add the ways from the previous state where personk
didn't have a hat, which isf[i-1][j ^ (1 << k)]
By processing all hats and tracking all possible assignment states, we can find the total number of ways to assign different hats to all n
people, which is f[m][(1 << n) - 1]
where m
is the highest numbered hat and (1 << n) - 1
represents all people having hats.
Learn more about Dynamic Programming and Bitmask patterns.
Solution Approach
We implement the dynamic programming solution using a 2D array where the first dimension represents hats and the second dimension represents the state of people (using bitmask).
Step 1: Build the preference mapping
g = defaultdict(list)
for i, h in enumerate(hats):
for v in h:
g[v].append(i)
We create a dictionary g
where g[hat]
contains the list of people who like that hat. This reverse mapping makes it easier to process hats one by one.
Step 2: Initialize the DP table
n = len(hats)
m = max(max(h) for h in hats)
f = [[0] * (1 << n) for _ in range(m + 1)]
f[0][0] = 1
n
is the number of peoplem
is the highest hat number that anyone likesf[i][j]
represents the number of ways to assign the firsti
hats to achieve statej
- We initialize
f[0][0] = 1
(one way to assign 0 hats to 0 people)
Step 3: Fill the DP table
for i in range(1, m + 1):
for j in range(1 << n):
f[i][j] = f[i - 1][j] # Case 1: Skip hat i
for k in g[i]: # Case 2: Assign hat i to person k
if j >> k & 1: # Check if person k is in state j
f[i][j] = (f[i][j] + f[i - 1][j ^ (1 << k)]) % mod
For each hat i
and each state j
:
- Case 1: We don't assign hat
i
to anyone, sof[i][j]
inherits fromf[i-1][j]
- Case 2: We try to assign hat
i
to each personk
who likes itj >> k & 1
checks if thek
-th bit ofj
is set (personk
has a hat in statej
)- If yes, we add the count from the previous state where person
k
didn't have a hat:f[i-1][j ^ (1 << k)]
- The XOR operation
j ^ (1 << k)
flips thek
-th bit, removing personk
from the state
Step 4: Return the result
return f[m][-1]
The answer is f[m][(1 << n) - 1]
, which represents the number of ways to assign up to m
hats such that all n
people have hats. Since (1 << n) - 1
is the last element in the array (all bits set), we can access it with f[m][-1]
.
The time complexity is O(m × 2^n × n)
where m
is the number of hats and n
is the number of people. The space complexity is O(m × 2^n)
.
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 where we have 2 people and their hat preferences:
- Person 0 likes hats [1, 2]
- Person 1 likes hats [2, 3]
Step 1: Build the reverse mapping
g[1] = [0] # Hat 1 is liked by person 0 g[2] = [0, 1] # Hat 2 is liked by persons 0 and 1 g[3] = [1] # Hat 3 is liked by person 1
Step 2: Initialize DP table
- n = 2 people, m = 3 (highest hat number)
- We need states from 00 to 11 in binary (0 to 3 in decimal)
- f[0][0] = 1 (one way to assign 0 hats to 0 people)
Step 3: Process each hat
Processing Hat 1:
- State 00 (nobody has hats): f[1][00] = f[0][00] = 1 (skip hat 1)
- State 01 (person 0 has hat):
- Skip: f[0][01] = 0
- Assign to person 0: f[0][00] = 1
- Total: f[1][01] = 0 + 1 = 1
- State 10 (person 1 has hat): f[1][10] = f[0][10] = 0 (person 1 doesn't like hat 1)
- State 11 (both have hats): f[1][11] = f[0][11] = 0
After hat 1: f[1] = [1, 1, 0, 0]
Processing Hat 2:
- State 00: f[2][00] = f[1][00] = 1 (skip hat 2)
- State 01:
- Skip: f[1][01] = 1
- Person 0 already has hat, can't assign
- Total: f[2][01] = 1
- State 10:
- Skip: f[1][10] = 0
- Assign to person 1: f[1][00] = 1
- Total: f[2][10] = 0 + 1 = 1
- State 11:
- Skip: f[1][11] = 0
- Assign to person 0: f[1][10] = 0
- Assign to person 1: f[1][01] = 1
- Total: f[2][11] = 0 + 0 + 1 = 1
After hat 2: f[2] = [1, 1, 1, 1]
Processing Hat 3:
- State 00: f[3][00] = f[2][00] = 1
- State 01: f[3][01] = f[2][01] = 1 (person 0 doesn't like hat 3)
- State 10:
- Skip: f[2][10] = 1
- Person 1 already has hat, can't assign
- Total: f[3][10] = 1
- State 11:
- Skip: f[2][11] = 1
- Assign to person 1: f[2][01] = 1
- Total: f[3][11] = 1 + 1 = 2
After hat 3: f[3] = [1, 1, 1, 2]
Result: f[3][11] = 2, meaning there are 2 ways to assign hats:
- Person 0 wears hat 1, person 1 wears hat 2
- Person 0 wears hat 2, person 1 wears hat 3
Solution Implementation
1class Solution:
2 def numberWays(self, hats: List[List[int]]) -> int:
3 # Build a mapping from each hat to the list of people who can wear it
4 hat_to_people = defaultdict(list)
5 for person_id, person_hats in enumerate(hats):
6 for hat in person_hats:
7 hat_to_people[hat].append(person_id)
8
9 MOD = 10**9 + 7
10 num_people = len(hats)
11 max_hat_id = max(max(person_hats) for person_hats in hats)
12
13 # dp[i][mask] = number of ways to assign first i hats to people
14 # where mask represents which people have been assigned hats
15 # bit j in mask = 1 means person j has been assigned a hat
16 dp = [[0] * (1 << num_people) for _ in range(max_hat_id + 1)]
17
18 # Base case: 0 hats considered, no one has a hat = 1 way
19 dp[0][0] = 1
20
21 # Process each hat from 1 to max_hat_id
22 for hat_id in range(1, max_hat_id + 1):
23 for people_mask in range(1 << num_people):
24 # Option 1: Don't use the current hat
25 dp[hat_id][people_mask] = dp[hat_id - 1][people_mask]
26
27 # Option 2: Assign current hat to one of the eligible people
28 for person in hat_to_people[hat_id]:
29 # Check if this person already has a hat in current mask
30 if people_mask >> person & 1:
31 # Add ways from previous state where this person didn't have a hat
32 previous_mask = people_mask ^ (1 << person)
33 dp[hat_id][people_mask] = (dp[hat_id][people_mask] +
34 dp[hat_id - 1][previous_mask]) % MOD
35
36 # Return the number of ways where all hats are considered and all people have hats
37 # The mask with all people having hats is (1 << num_people) - 1 = all bits set
38 return dp[max_hat_id][(1 << num_people) - 1]
39
1class Solution {
2 public int numberWays(List<List<Integer>> hats) {
3 // Number of people
4 int numPeople = hats.size();
5
6 // Find the maximum hat number to determine the range of hats
7 int maxHatNumber = 0;
8 for (List<Integer> personHats : hats) {
9 for (int hatNumber : personHats) {
10 maxHatNumber = Math.max(maxHatNumber, hatNumber);
11 }
12 }
13
14 // Create adjacency list: hatToPeople[i] contains list of people who can wear hat i
15 List<Integer>[] hatToPeople = new List[maxHatNumber + 1];
16 Arrays.setAll(hatToPeople, index -> new ArrayList<>());
17
18 // Populate the adjacency list
19 for (int personId = 0; personId < numPeople; ++personId) {
20 for (int hatNumber : hats.get(personId)) {
21 hatToPeople[hatNumber].add(personId);
22 }
23 }
24
25 // Modulo value for the result
26 final int MOD = (int) 1e9 + 7;
27
28 // DP table: dp[i][mask] = number of ways to assign first i hats to people represented by mask
29 // mask is a bitmask where bit j is 1 if person j has been assigned a hat
30 int[][] dp = new int[maxHatNumber + 1][1 << numPeople];
31
32 // Base case: 0 hats assigned to 0 people = 1 way
33 dp[0][0] = 1;
34
35 // Fill the DP table
36 for (int hatId = 1; hatId <= maxHatNumber; ++hatId) {
37 for (int mask = 0; mask < (1 << numPeople); ++mask) {
38 // Option 1: Don't assign current hat to anyone
39 dp[hatId][mask] = dp[hatId - 1][mask];
40
41 // Option 2: Try assigning current hat to each eligible person
42 for (int personId : hatToPeople[hatId]) {
43 // Check if this person is already wearing a hat (bit is set in mask)
44 if ((mask >> personId & 1) == 1) {
45 // Add ways where this person wasn't wearing a hat before
46 int previousMask = mask ^ (1 << personId);
47 dp[hatId][mask] = (dp[hatId][mask] + dp[hatId - 1][previousMask]) % MOD;
48 }
49 }
50 }
51 }
52
53 // Return the number of ways to assign all hats to all people
54 // (1 << numPeople) - 1 creates a mask with all bits set (all people have hats)
55 return dp[maxHatNumber][(1 << numPeople) - 1];
56 }
57}
58
1class Solution {
2public:
3 int numberWays(vector<vector<int>>& hats) {
4 // Get the number of people
5 int numPeople = hats.size();
6
7 // Find the maximum hat number to determine the range of hats
8 int maxHatNumber = 0;
9 for (auto& personHats : hats) {
10 maxHatNumber = max(maxHatNumber, *max_element(personHats.begin(), personHats.end()));
11 }
12
13 // Build reverse mapping: for each hat, store which people can wear it
14 vector<vector<int>> hatToPeople(maxHatNumber + 1);
15 for (int personId = 0; personId < numPeople; ++personId) {
16 for (int& hatId : hats[personId]) {
17 hatToPeople[hatId].push_back(personId);
18 }
19 }
20
21 // Modulo value for the result
22 const int MOD = 1e9 + 7;
23
24 // DP table: dp[i][mask] = number of ways to assign first i hats to people in mask
25 // where mask is a bitmask representing which people have been assigned hats
26 int dp[maxHatNumber + 1][1 << numPeople];
27 memset(dp, 0, sizeof(dp));
28
29 // Base case: no hats assigned, no people wearing hats
30 dp[0][0] = 1;
31
32 // Process each hat from 1 to maxHatNumber
33 for (int hatId = 1; hatId <= maxHatNumber; ++hatId) {
34 // For each possible state (bitmask) of people wearing hats
35 for (int peopleMask = 0; peopleMask < (1 << numPeople); ++peopleMask) {
36 // Option 1: Don't assign current hat to anyone
37 dp[hatId][peopleMask] = dp[hatId - 1][peopleMask];
38
39 // Option 2: Try assigning current hat to each eligible person
40 for (int personId : hatToPeople[hatId]) {
41 // Check if this person is already wearing a hat in the current mask
42 if ((peopleMask >> personId) & 1) {
43 // Add ways from previous state where this person wasn't wearing a hat
44 int previousMask = peopleMask ^ (1 << personId);
45 dp[hatId][peopleMask] = (dp[hatId][peopleMask] + dp[hatId - 1][previousMask]) % MOD;
46 }
47 }
48 }
49 }
50
51 // Return the number of ways where all people are wearing hats
52 // (all bits set in the mask)
53 return dp[maxHatNumber][(1 << numPeople) - 1];
54 }
55};
56
1function numberWays(hats: number[][]): number {
2 // Number of people
3 const numPeople = hats.length;
4
5 // Maximum hat id (number of unique hats)
6 const maxHatId = Math.max(...hats.flat());
7
8 // Build adjacency list: hatToPeople[hatId] = list of people who can wear this hat
9 const hatToPeople: number[][] = Array.from({ length: maxHatId + 1 }, () => []);
10 for (let personId = 0; personId < numPeople; ++personId) {
11 for (const hatId of hats[personId]) {
12 hatToPeople[hatId].push(personId);
13 }
14 }
15
16 // DP table: dp[hatId][peopleMask] = number of ways to assign hats 1 to hatId
17 // where peopleMask represents which people have been assigned hats
18 const dp: number[][] = Array.from({ length: maxHatId + 1 }, () =>
19 Array.from({ length: 1 << numPeople }, () => 0)
20 );
21
22 // Base case: no hats assigned, no people wearing hats
23 dp[0][0] = 1;
24
25 const MOD = 1e9 + 7;
26
27 // Iterate through each hat
28 for (let hatId = 1; hatId <= maxHatId; ++hatId) {
29 // Iterate through all possible people assignment states
30 for (let peopleMask = 0; peopleMask < (1 << numPeople); ++peopleMask) {
31 // Option 1: Don't assign current hat to anyone
32 dp[hatId][peopleMask] = dp[hatId - 1][peopleMask];
33
34 // Option 2: Try assigning current hat to each eligible person
35 for (const personId of hatToPeople[hatId]) {
36 // Check if this person is already wearing a hat in current state
37 if (((peopleMask >> personId) & 1) === 1) {
38 // Add ways from previous state where this person wasn't wearing a hat
39 const previousMask = peopleMask ^ (1 << personId);
40 dp[hatId][peopleMask] = (dp[hatId][peopleMask] + dp[hatId - 1][previousMask]) % MOD;
41 }
42 }
43 }
44 }
45
46 // Return number of ways where all people are wearing hats
47 const allPeopleWearingHats = (1 << numPeople) - 1;
48 return dp[maxHatId][allPeopleWearingHats];
49}
50
Time and Space Complexity
Time Complexity: O(m × 2^n × n)
The algorithm uses dynamic programming with states f[i][j]
where i
represents hats from 1 to m
and j
represents a bitmask of size 2^n
for people assignments.
- The outer loop iterates through
m
hats:O(m)
- The middle loop iterates through all possible bitmasks:
O(2^n)
- The inner loop iterates through people who can wear hat
i
, which in the worst case can be alln
people:O(n)
Therefore, the overall time complexity is O(m × 2^n × n)
.
Space Complexity: O(m × 2^n)
The algorithm uses a 2D DP array f
with dimensions (m + 1) × 2^n
:
- First dimension has size
m + 1
for hats from 0 tom
- Second dimension has size
2^n
for all possible bitmask states
Additionally, the graph g
stores the mapping of hats to people, which takes O(m × n)
space in the worst case.
Since O(m × 2^n) > O(m × n)
when n > 1
, the dominant space complexity is O(m × 2^n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect State Representation - Processing People Instead of Hats
A natural but inefficient approach is to use dp[person][hats_used_mask]
where we process people one by one and track which hats have been used. This leads to a state space of O(n × 2^40)
which is computationally infeasible since 2^40 is enormous.
Why this happens: It's intuitive to think "assign hats to people" and process people sequentially.
Solution: Reverse the perspective - process hats one by one and track which people have received hats. This gives us O(40 × 2^n)
states, which is manageable since n ≤ 10.
2. Off-by-One Error in DP Array Initialization
A common mistake is initializing the DP array incorrectly:
# Wrong - doesn't account for hat 0 (which doesn't exist)
dp = [[0] * (1 << num_people) for _ in range(max_hat_id)]
Why this happens: Forgetting that we need an extra row for the base case (0 hats considered).
Solution: Initialize with max_hat_id + 1
rows:
dp = [[0] * (1 << num_people) for _ in range(max_hat_id + 1)]
3. Incorrect Bit Manipulation for Previous State
When calculating the previous state, using wrong operations:
# Wrong - using OR instead of XOR previous_mask = people_mask | (1 << person) # Wrong - using AND instead of XOR previous_mask = people_mask & ~(1 << person)
Why this happens: Confusion about how to remove a person from the bitmask.
Solution: Use XOR to flip the bit:
previous_mask = people_mask ^ (1 << person)
This correctly removes person from the current mask to get the previous state.
4. Forgetting the Modulo Operation
Missing the modulo operation during state transitions:
# Wrong - might cause integer overflow dp[hat_id][people_mask] = dp[hat_id][people_mask] + dp[hat_id - 1][previous_mask]
Why this happens: Focusing on the logic and forgetting about the constraint that answers can be very large.
Solution: Apply modulo after each addition:
dp[hat_id][people_mask] = (dp[hat_id][people_mask] + dp[hat_id - 1][previous_mask]) % MOD
5. Using 1-indexed Hats with 0-indexed Arrays
Confusion between hat numbering (1 to 40) and array indexing (0-based):
# Wrong - treating hat numbers as 0-indexed
for hat_id in range(max_hat_id): # Missing hat 40 if max_hat_id is 40
Why this happens: The problem states hats are numbered 1-40, but arrays are 0-indexed.
Solution: Be consistent - either adjust indices when building the mapping or ensure loops cover the correct range:
for hat_id in range(1, max_hat_id + 1): # Correctly processes hats 1 to max_hat_id
Which technique can we use to find the middle of a linked list?
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!