1820. Maximum Number of Accepted Invitations π
Problem Description
You have a class with m
boys and n
girls who are attending an upcoming party. You need to determine the maximum number of boy-girl pairs that can be formed for the party.
You're given an m x n
matrix called grid
where each element is either 0
or 1
. If grid[i][j] == 1
, this means the i-th
boy can invite the j-th
girl to the party. If grid[i][j] == 0
, the i-th
boy cannot invite the j-th
girl.
The matching rules are:
- Each boy can invite at most one girl
- Each girl can accept at most one invitation from a boy
Your task is to find the maximum number of successful invitations (matched pairs) possible.
For example, if you have 3 boys and 3 girls with a grid like:
[[1, 1, 0], [1, 0, 1], [0, 0, 1]]
Boy 0 can invite girls 0 or 1, boy 1 can invite girls 0 or 2, and boy 2 can only invite girl 2. The maximum number of matches would be 3 (boy 0 β girl 1, boy 1 β girl 0, boy 2 β girl 2).
This is a classic bipartite matching problem where you need to find the maximum number of edges in a matching between two disjoint sets (boys and girls), with the constraint that no vertex is connected to more than one edge in the matching.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: This problem can be modeled as a bipartite graph where boys form one set of vertices and girls form another set. The edges represent possible invitations (when
grid[i][j] == 1
).
Is it a tree?
- No: The graph is not a tree. It's a bipartite graph where multiple boys can potentially invite the same girl, and vice versa, creating a more complex structure than a tree.
Is the problem related to directed acyclic graphs?
- No: While we could model this as a directed graph (invitations go from boys to girls), the problem is fundamentally about finding maximum matching in a bipartite graph, not about DAG properties.
Is the problem related to shortest paths?
- No: We're not looking for shortest paths between nodes. We're looking for maximum matching.
Does the problem involve connectivity?
- No: We're not checking if nodes are connected or finding connected components. We're finding the maximum number of valid pairings.
Does the problem have small constraints?
- Yes: The constraints are typically small enough (usually m, n β€ 100 or similar) that we can use algorithms with higher complexity like the Hungarian algorithm which uses DFS to find augmenting paths.
DFS/backtracking?
- Yes: The solution uses DFS to find augmenting paths in the bipartite graph. Each DFS call tries to find an unmatched girl or a girl whose current match can be reassigned to create a new valid matching.
Conclusion: The flowchart leads us to use DFS/backtracking approach. The Hungarian algorithm implemented in the solution uses DFS to repeatedly find augmenting paths - paths that start from an unmatched boy and end at an unmatched girl (or a girl whose current partner can find another match). This DFS-based approach efficiently solves the maximum bipartite matching problem.
Intuition
Think of this problem like a matchmaking scenario at a dance party. We have boys who want to invite girls, but each person can only be paired with one partner. The challenge is to maximize the total number of pairs.
The key insight is recognizing this as a bipartite matching problem. We have two distinct groups (boys and girls), and we want to create the maximum number of one-to-one connections between them.
Why does the DFS approach work here? Let's break down the intuition:
-
Start Simple: For each boy, we try to find a girl he can invite. If we find an available girl (not yet matched), great! We make the match.
-
The Clever Part - Augmenting Paths: What if all girls a boy can invite are already matched? This is where DFS shines. Instead of giving up, we ask: "Can we rearrange existing matches to make room for this boy?"
-
Chain Reaction: When boy
i
wants to invite girlj
who's already matched with boyk
, we use DFS to check if boyk
can find another girl. If he can, we create a chain reaction:- Boy
k
switches to a different girl - This frees up girl
j
- Now boy
i
can invite girlj
- Boy
-
Why This Works: Each DFS call tries to find what's called an "augmenting path" - a path that increases the total number of matches by 1. We keep finding these paths until no more exist, guaranteeing we've found the maximum matching.
The vis
set in the code prevents us from going in circles during each DFS attempt, ensuring we don't try the same girl twice in one matching attempt. The match
array keeps track of which boy each girl is currently paired with (-1
means unpaired).
This greedy approach of trying to match each boy one by one, combined with the flexibility to rearrange previous matches through DFS, ensures we find the optimal solution without having to explore all possible combinations explicitly.
Learn more about Depth-First Search and Graph patterns.
Solution Approach
The solution implements the Hungarian Algorithm for maximum bipartite matching. Let's walk through the implementation step by step:
Data Structures Used:
match
: An array of sizen
wherematch[j]
stores which boy is currently matched with girlj
. Initially set to-1
(unmatched).vis
: A set that tracks which girls have been visited during the current DFS attempt to avoid cycles.
Algorithm Implementation:
-
Main Loop Structure:
for i in range(m): vis = set() ans += find(i)
For each boy
i
, we attempt to find a match. We reset thevis
set for each boy to allow exploring all possible augmenting paths. -
The DFS Function (
find
):def find(i): for j, v in enumerate(grid[i]): if v and j not in vis:
This function tries to find a match for boy
i
. It iterates through all girlsj
that boyi
can invite (grid[i][j] == 1
) and haven't been visited in this DFS path. -
Marking Visited:
vis.add(j)
We mark girl
j
as visited to prevent revisiting her in the same DFS path, avoiding infinite loops. -
The Core Matching Logic:
if match[j] == -1 or find(match[j]): match[j] = i return True
This is where the magic happens:
- Case 1:
match[j] == -1
- Girlj
is unmatched. We can directly pair her with boyi
. - Case 2:
find(match[j])
- Girlj
is already matched with some boymatch[j]
. We recursively try to find an alternative match for boymatch[j]
. If successful, we can "steal" girlj
for boyi
.
- Case 1:
-
Building the Answer: The algorithm processes boys one by one. Each successful
find(i)
call increases the total matches by 1. The sum of all successful matches gives us the maximum number of accepted invitations.
Why This Works:
The algorithm guarantees finding the maximum matching because:
- It systematically explores all augmenting paths (paths that can increase the matching size)
- The DFS ensures we explore all possible rearrangements of existing matches
- Processing boys sequentially and maintaining the
match
array ensures we never decrease the matching size - When no more augmenting paths exist, we've reached the maximum matching
Time Complexity:
- Each DFS can visit up to
n
girls: O(n) - We run DFS for each of the
m
boys: O(m) - Total: O(m Γ n Γ n) in the worst case, though typically much better in practice
The elegance of this solution lies in its simplicity - just 15 lines of code to solve a complex matching problem optimally!
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 to illustrate how the DFS-based bipartite matching works.
Example Grid:
grid = [[1, 1, 0], [0, 1, 1], [1, 0, 0]]
This means:
- Boy 0 can invite girls 0 or 1
- Boy 1 can invite girls 1 or 2
- Boy 2 can invite only girl 0
Initial State:
match = [-1, -1, -1]
(all girls unmatched)ans = 0
Step 1: Process Boy 0
vis = {}
- Try to find a match for boy 0
- Check girl 0:
grid[0][0] = 1
, girl 0 not visited- Add girl 0 to
vis
:vis = {0}
match[0] == -1
(girl 0 is unmatched)- Set
match[0] = 0
(pair boy 0 with girl 0) - Return
True
- Add girl 0 to
ans = 1
,match = [0, -1, -1]
Step 2: Process Boy 1
vis = {}
(reset for new boy)- Try to find a match for boy 1
- Skip girl 0:
grid[1][0] = 0
- Check girl 1:
grid[1][1] = 1
, girl 1 not visited- Add girl 1 to
vis
:vis = {1}
match[1] == -1
(girl 1 is unmatched)- Set
match[1] = 1
(pair boy 1 with girl 1) - Return
True
- Add girl 1 to
ans = 2
,match = [0, 1, -1]
Step 3: Process Boy 2
vis = {}
(reset for new boy)- Try to find a match for boy 2
- Check girl 0:
grid[2][0] = 1
, girl 0 not visited- Add girl 0 to
vis
:vis = {0}
match[0] == 0
(girl 0 is already matched with boy 0)- Recursively call
find(0)
to see if boy 0 can find another match:- In the recursive call for boy 0:
- Skip girl 0: already in
vis = {0}
- Check girl 1:
grid[0][1] = 1
, girl 1 not invis
- Add girl 1 to
vis
:vis = {0, 1}
match[1] == 1
(girl 1 is matched with boy 1)- Recursively call
find(1)
to see if boy 1 can find another match:- In the recursive call for boy 1:
- Skip girl 0:
grid[1][0] = 0
- Skip girl 1: already in
vis = {0, 1}
- Check girl 2:
grid[1][2] = 1
, girl 2 not invis
- Add girl 2 to
vis
:vis = {0, 1, 2}
match[2] == -1
(girl 2 is unmatched)- Set
match[2] = 1
(boy 1 switches to girl 2) - Return
True
- Skip girl 0:
- In the recursive call for boy 1:
- Since boy 1 found alternative (girl 2), set
match[1] = 0
(boy 0 takes girl 1) - Return
True
- Skip girl 0: already in
- In the recursive call for boy 0:
- Since boy 0 found alternative (girl 1), set
match[0] = 2
(boy 2 takes girl 0) - Return
True
- Add girl 0 to
ans = 3
,match = [2, 0, 1]
Final Result:
- Maximum matches: 3
- Final pairing:
- Boy 2 β Girl 0
- Boy 0 β Girl 1
- Boy 1 β Girl 2
The key insight here is the chain reaction in Step 3: Boy 2 wanted girl 0, but she was taken. Through DFS, we found that if boy 0 switches to girl 1, and boy 1 switches to girl 2, then boy 2 can have girl 0. This cascading rearrangement is what makes the algorithm find the optimal solution!
Solution Implementation
1class Solution:
2 def maximumInvitations(self, grid: List[List[int]]) -> int:
3 """
4 Solves the maximum bipartite matching problem using Hungarian algorithm.
5 Each boy (row) can be matched with at most one girl (column) based on grid preferences.
6
7 Args:
8 grid: 2D list where grid[i][j] = 1 means boy i can invite girl j
9
10 Returns:
11 Maximum number of successful invitations (matches)
12 """
13
14 def dfs_augmenting_path(boy_idx: int) -> bool:
15 """
16 Attempts to find an augmenting path starting from boy_idx using DFS.
17
18 Args:
19 boy_idx: Index of the current boy trying to find a match
20
21 Returns:
22 True if an augmenting path is found, False otherwise
23 """
24 # Try each girl that this boy can invite
25 for girl_idx, can_invite in enumerate(grid[boy_idx]):
26 # Check if boy can invite this girl and she hasn't been visited in this round
27 if can_invite and girl_idx not in visited_girls:
28 # Mark this girl as visited to avoid cycles
29 visited_girls.add(girl_idx)
30
31 # If girl is unmatched or we can reassign her current match
32 if girl_to_boy_match[girl_idx] == -1 or dfs_augmenting_path(girl_to_boy_match[girl_idx]):
33 # Update the matching: assign this girl to current boy
34 girl_to_boy_match[girl_idx] = boy_idx
35 return True
36
37 return False
38
39 # Get dimensions: m boys and n girls
40 num_boys, num_girls = len(grid), len(grid[0])
41
42 # Initialize matching array: girl_to_boy_match[j] = i means girl j is matched with boy i
43 # -1 means the girl is unmatched
44 girl_to_boy_match = [-1] * num_girls
45
46 # Count total successful matches
47 total_matches = 0
48
49 # Try to find a match for each boy
50 for boy_idx in range(num_boys):
51 # Reset visited set for each boy's matching attempt
52 visited_girls = set()
53
54 # If we find an augmenting path, increment the match count
55 if dfs_augmenting_path(boy_idx):
56 total_matches += 1
57
58 return total_matches
59
1class Solution {
2 // Grid representing the bipartite graph (boys to girls preferences)
3 private int[][] grid;
4 // Visited array to track girls visited in current DFS path
5 private boolean[] visited;
6 // Array to store which boy is matched to each girl (-1 if unmatched)
7 private int[] girlToBoymatch;
8 // Number of girls (columns in grid)
9 private int numGirls;
10
11 /**
12 * Finds maximum number of invitations using Hungarian algorithm / Maximum Bipartite Matching
13 * @param grid 2D array where grid[i][j] = 1 means boy i can invite girl j
14 * @return Maximum number of successful invitations
15 */
16 public int maximumInvitations(int[][] grid) {
17 int numBoys = grid.length;
18 numGirls = grid[0].length;
19 this.grid = grid;
20
21 // Initialize visited array for DFS traversal
22 visited = new boolean[numGirls];
23
24 // Initialize matching array, -1 means girl is unmatched
25 girlToBoymatch = new int[numGirls];
26 Arrays.fill(girlToBoymatch, -1);
27
28 int maxMatches = 0;
29
30 // Try to find an augmenting path for each boy
31 for (int boy = 0; boy < numBoys; boy++) {
32 // Reset visited array for each boy's DFS
33 Arrays.fill(visited, false);
34
35 // If we can find an augmenting path starting from this boy
36 if (find(boy)) {
37 maxMatches++;
38 }
39 }
40
41 return maxMatches;
42 }
43
44 /**
45 * DFS to find an augmenting path starting from given boy
46 * @param boy The boy index to find a match for
47 * @return true if an augmenting path is found, false otherwise
48 */
49 private boolean find(int boy) {
50 // Try to match with each girl
51 for (int girl = 0; girl < numGirls; girl++) {
52 // Check if boy can invite this girl and girl hasn't been visited in current path
53 if (grid[boy][girl] == 1 && !visited[girl]) {
54 // Mark girl as visited to avoid cycles in current DFS
55 visited[girl] = true;
56
57 // If girl is unmatched OR we can reassign her current match to another girl
58 if (girlToBoymatch[girl] == -1 || find(girlToBoymatch[girl])) {
59 // Match this boy with this girl
60 girlToBoymatch[girl] = boy;
61 return true;
62 }
63 }
64 }
65
66 // No augmenting path found
67 return false;
68 }
69}
70
1class Solution {
2public:
3 int maximumInvitations(vector<vector<int>>& grid) {
4 int numBoys = grid.size();
5 int numGirls = grid[0].size();
6
7 // visited[j] tracks if girl j has been visited in current DFS path
8 bool visited[210];
9
10 // girlMatch[j] stores which boy is matched to girl j (-1 if unmatched)
11 int girlMatch[210];
12 memset(girlMatch, -1, sizeof(girlMatch));
13
14 int maxMatches = 0;
15
16 // Hungarian algorithm: find augmenting path starting from boy i
17 function<bool(int)> findAugmentingPath = [&](int boy) -> bool {
18 // Try to match current boy with each girl he's interested in
19 for (int girl = 0; girl < numGirls; ++girl) {
20 // Check if boy is interested in girl and girl hasn't been visited in this path
21 if (grid[boy][girl] && !visited[girl]) {
22 visited[girl] = true;
23
24 // If girl is unmatched or we can find an augmenting path
25 // from the boy currently matched to this girl
26 if (girlMatch[girl] == -1 || findAugmentingPath(girlMatch[girl])) {
27 girlMatch[girl] = boy; // Match current boy with this girl
28 return true;
29 }
30 }
31 }
32 return false; // No augmenting path found
33 };
34
35 // Try to find a match for each boy
36 for (int boy = 0; boy < numBoys; ++boy) {
37 // Reset visited array for each new boy (new DFS search)
38 memset(visited, 0, sizeof(visited));
39
40 // If we find an augmenting path, increment the match count
41 if (findAugmentingPath(boy)) {
42 maxMatches++;
43 }
44 }
45
46 return maxMatches;
47 }
48};
49
1function maximumInvitations(grid: number[][]): number {
2 const numBoys = grid.length;
3 const numGirls = grid[0].length;
4
5 // girlMatch[j] stores which boy is matched to girl j (-1 if unmatched)
6 const girlMatch: number[] = new Array(numGirls).fill(-1);
7
8 // visited[j] tracks if girl j has been visited in current DFS path
9 let visited: boolean[] = [];
10
11 let maxMatches = 0;
12
13 // Hungarian algorithm: find augmenting path starting from boy
14 const findAugmentingPath = (boy: number): boolean => {
15 // Try to match current boy with each girl he's interested in
16 for (let girl = 0; girl < numGirls; girl++) {
17 // Check if boy is interested in girl and girl hasn't been visited in this path
18 if (grid[boy][girl] === 1 && !visited[girl]) {
19 visited[girl] = true;
20
21 // If girl is unmatched or we can find an augmenting path
22 // from the boy currently matched to this girl
23 if (girlMatch[girl] === -1 || findAugmentingPath(girlMatch[girl])) {
24 girlMatch[girl] = boy; // Match current boy with this girl
25 return true;
26 }
27 }
28 }
29 return false; // No augmenting path found
30 };
31
32 // Try to find a match for each boy
33 for (let boy = 0; boy < numBoys; boy++) {
34 // Reset visited array for each new boy (new DFS search)
35 visited = new Array(numGirls).fill(false);
36
37 // If we find an augmenting path, increment the match count
38 if (findAugmentingPath(boy)) {
39 maxMatches++;
40 }
41 }
42
43 return maxMatches;
44}
45
Time and Space Complexity
Time Complexity: O(mΒ² Γ n)
The algorithm implements the Hungarian algorithm (bipartite matching using augmenting paths). For each of the m
boys (rows), we call the find
function which attempts to find an augmenting path. In the worst case, find
can recursively call itself up to m
times (when it needs to reassign previously matched girls). Within each find
call, we iterate through all n
girls (columns). Therefore, the overall time complexity is O(m Γ m Γ n) = O(mΒ² Γ n)
.
Note: The reference answer states O(m Γ n)
, which would be the complexity if we only considered the iteration through the grid without accounting for the recursive depth of the augmenting path search. However, the actual worst-case complexity for this bipartite matching algorithm is O(mΒ² Γ n)
.
Space Complexity: O(m + n)
The space complexity consists of:
match
array of sizen
:O(n)
vis
set which can contain at mostn
elements:O(n)
- Recursion stack depth which can go up to
m
in the worst case:O(m)
Therefore, the total space complexity is O(m + n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Resetting the Visited Set for Each Boy
The Pitfall:
A common mistake is to use a global visited
set that doesn't get reset between different boys' matching attempts:
# WRONG APPROACH
def maximumInvitations(self, grid):
visited_girls = set() # Global visited set - WRONG!
def dfs_augmenting_path(boy_idx):
for girl_idx, can_invite in enumerate(grid[boy_idx]):
if can_invite and girl_idx not in visited_girls:
visited_girls.add(girl_idx)
# ... rest of logic
for boy_idx in range(num_boys):
# visited_girls never gets reset!
if dfs_augmenting_path(boy_idx):
total_matches += 1
Why This Fails:
- Once a girl is visited while finding a match for one boy, she becomes permanently marked as visited
- Subsequent boys cannot explore paths through that girl, even though she might be part of their optimal augmenting path
- This leads to suboptimal matching results
Example Where It Breaks:
Grid: [[1, 1], [0, 1]] With global visited: - Boy 0 tries girl 0 (marks visited), gets matched - Boy 0 tries girl 1 (marks visited), but girl 1 is harder to match - Boy 1 can only invite girl 1, but she's already visited! - Result: Only 1 match instead of 2
The Solution: Always reset the visited set for each boy's matching attempt:
for boy_idx in range(num_boys):
visited_girls = set() # Reset for each boy - CORRECT!
if dfs_augmenting_path(boy_idx):
total_matches += 1
2. Confusing Match Direction (Boy-to-Girl vs Girl-to-Boy)
The Pitfall: Mixing up the matching array's direction can lead to incorrect implementations:
# WRONG: Using boy_to_girl_match instead of girl_to_boy_match
boy_to_girl_match = [-1] * num_boys # WRONG direction!
def dfs_augmenting_path(boy_idx):
for girl_idx, can_invite in enumerate(grid[boy_idx]):
if can_invite and girl_idx not in visited_girls:
visited_girls.add(girl_idx)
# This logic doesn't work with boy_to_girl array
if boy_to_girl_match[boy_idx] == -1: # WRONG!
boy_to_girl_match[boy_idx] = girl_idx
return True
Why This Fails:
- We need to track which boy each girl is matched with (not vice versa)
- This allows us to check if a girl is available and potentially reassign her current match
- Using boy-to-girl matching makes it impossible to check if a specific girl is already matched
The Solution:
Use girl_to_boy_match
array where index represents the girl and value represents the matched boy:
girl_to_boy_match = [-1] * num_girls # CORRECT! # girl_to_boy_match[j] = i means girl j is matched with boy i
3. Forgetting to Check Grid Boundaries
The Pitfall: Assuming the grid is always non-empty or square:
# WRONG: No validation
def maximumInvitations(self, grid):
num_boys, num_girls = len(grid), len(grid[0]) # Crashes if grid is empty!
The Solution: Add proper boundary checks:
def maximumInvitations(self, grid):
if not grid or not grid[0]:
return 0
num_boys, num_girls = len(grid), len(grid[0])
4. Not Understanding When to Return True in DFS
The Pitfall: Returning True immediately when finding any available girl, without trying to reassign:
# WRONG: Greedy approach without reassignment
def dfs_augmenting_path(boy_idx):
for girl_idx, can_invite in enumerate(grid[boy_idx]):
if can_invite and girl_to_boy_match[girl_idx] == -1:
girl_to_boy_match[girl_idx] = boy_idx
return True # WRONG: Not trying to reassign already matched girls
return False
Why This Fails: This greedy approach might miss optimal solutions where reassigning existing matches leads to more total matches.
The Solution: Always attempt reassignment through recursive DFS:
if girl_to_boy_match[girl_idx] == -1 or dfs_augmenting_path(girl_to_boy_match[girl_idx]): girl_to_boy_match[girl_idx] = boy_idx return True
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
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!