Smallest Sufficient Team

Updated on 09 July, 2025
Smallest Sufficient Team header image

Problem Statement

In the context of a project, there exists a set of specific skills, listed under req_skills, that are necessary for project completion. Associated with the project is a list of individuals, each denoted by people[i], where each person possesses a distinct set of skills. The challenge lies in assembling a project team, referred to as a "sufficient team," in such a manner that each required skill in req_skills is covered by at least one member of the team. The target is to identify the smallest team possible, represented by the indices of the persons in the list people. The solution should allow any valid team that meets the criteria, and it should be returned in any order. The problem guarantees that there is always at least one valid team that can be formed.

Examples

Example 1

Input:

req_skills = ["java","nodejs","reactjs"], people = [["java"],["nodejs"],["nodejs","reactjs"]]

Output:

[0,2]

Example 2

Input:

req_skills = ["algorithms","math","java","reactjs","csharp","aws"], people = [["algorithms","math","java"],["algorithms","math","reactjs"],["java","csharp","aws"],["reactjs","csharp"],["csharp","math"],["aws","java"]]

Output:

[1,2]

Constraints

  • 1 <= req_skills.length <= 16
  • 1 <= req_skills[i].length <= 16
  • req_skills[i] consists of lowercase English letters.
  • All the strings of req_skills are unique.
  • 1 <= people.length <= 60
  • 0 <= people[i].length <= 16
  • 1 <= people[i][j].length <= 16
  • people[i][j] consists of lowercase English letters.
  • All the strings of people[i] are unique.
  • Every skill in people[i] is a skill in req_skills.
  • It is guaranteed a sufficient team exists.

Approach and Intuition

To address the problem, it's essential to understand the relationship between the required skills and the skills each person brings. Here's a step-by-step approach based on the problem examples:

  1. Represent Skills with Bit Masks:
  • Each skill in req_skills can be represented as a binary number (bit mask). This transforms the problem into a more computationally manageable form, allowing us to use bitwise operations to easily determine skill coverage and overlap.
  1. Map People to Skills:
  • Each person's skills can be mapped in the same way, allowing us to create a list where each individual is represented by a bit mask indicating their skills.
  1. Dynamic Programming to Find Minimum Team:
  • Use a dynamic programming approach where the state dp[mask] represents the minimum team (smallest list of indices of people) needed to cover the skills represented by mask.
  • Initialize dp[0] as an empty team (base case: no skills required, no team members needed).
  • For each person and for each current state of the skills mask, update the state by combining the person's skills with the current state.
  • Check and store only the smallest team required for each skill combination.
  1. Iterate Through Combinations:
  • By iterating through all combinations of people and updating the dp array accordingly, we can eventually determine the smallest set of indices required to cover all skills by examining dp[final_mask] where final_mask represents all required skills.
  1. Backtrack to Form the Result:
  • Using the information in the dp array, backtrack to find which set of people (indices) form the smallest valid team.

The key constraint that helps in managing the complexity of the problem is the limit on the number of skills (req_skills.length <= 16). This permits the use of a bit-mask approach to represent subsets of skills, since there are at most (2^{16}) subsets (quite large but computationally feasible with optimized approaches). Furthermore, the gauranteed existence of a solution simplifies the problem to finding the optimal solution, rather than handling cases where no solution is possible.

Solutions

  • C++
cpp
class Solution {
public:
    vector<int> optimalTeamSelection(vector<string>& requiredSkills, vector<vector<string>>& teamMembers) {
        int totalPeople = teamMembers.size(), totalSkills = requiredSkills.size();
        unordered_map<string, int> skillMapping;
        for (int i = 0; i < totalSkills; i++) {
            skillMapping[requiredSkills[i]] = i;
        }
        vector<int> personSkillsMask(totalPeople);
        vector<long long> maskDP(1 << totalSkills, -1);
        for (int i = 0; i < totalPeople; i++) {
            for (string& skill : teamMembers[i]) {
                personSkillsMask[i] |= 1 << skillMapping[skill];
            }
        }
        function<long long(int)> calculateMask = [&](int skillMask) -> long long {
            if (skillMask == 0) return 0;
            if (maskDP[skillMask] != -1) return maskDP[skillMask];
            for (int i = 0; i < totalPeople; i++) {
                int reducedMask = skillMask & ~personSkillsMask[i];
                if (reducedMask != skillMask) {
                    long long tmpMask = calculateMask(reducedMask) | (1LL << i);
                    if (maskDP[skillMask] == -1 || __builtin_popcountll(tmpMask) <
                                                    __builtin_popcountll(maskDP[skillMask])) {
                        maskDP[skillMask] = tmpMask;
                    }
                }
            }
            return maskDP[skillMask];
        };
        long long finalMask = calculateMask((1 << totalSkills) - 1);
        vector<int> selectedMembers;
        for (int i = 0; i < totalPeople; i++) {
            if ((finalMask >> i) & 1) {
                selectedMembers.push_back(i);
            }
        }
        return selectedMembers;
    }
};

The provided C++ code defines a method to solve the "Smallest Sufficient Team" problem, using a bit manipulation and dynamic programming approach. The method optimalTeamSelection takes two arguments: requiredSkills, a list of skills needed for the project, and teamMembers, a list representing the skills each team member possesses.

  • The method first maps each skill to a unique integer using a hash map skillMapping.
  • It then creates a bitmask personSkillsMask for each person. This mask indicates which skills each person has.

Using dynamic programming, the solution utilizes a recursive function calculateMask that calculates the minimum team required to cover a given set of skills represented by skillMask:

  1. Base case: If skillMask is zero, no skills are needed, and the team size is zero.
  2. Memoization: Use maskDP array to store and reuse results for subproblems.
  3. Recursive Case: The function loops through each person, and for each, calculates a reduced skill set that would remain if this person were added to the team. Then it recursively calculates the minimum team size for this reduced skill set. The recursion ensures combination of current person's skills with previously computed smaller subproblems, aiming for a smaller team size.

The final steps consist of calculating the optimal mask representing the smallest team (finalMask) and translating this mask back into team member indices for the answer.

This approach is efficient for problems where the number of skills and team members is manageable, given its exponential nature in terms of the number of skills. The solution smartly reduces complexity using bit manipulation and dynamic programming techniques to focus only on relevant combinations of team members and required skills, making it a practical choice in constrained scenarios.

  • Java
java
class Solution {
    int totalPersons;
    int personSkillsMask[];
    long memo[];
    private Long calculateMinimumTeam(int currentMask) {
        if (currentMask == 0) {
            return 0L;
        }
        if (memo[currentMask] != -1) {
            return memo[currentMask];
        }
        for (int i = 0; i < totalPersons; i++) {
            int reducedMask = currentMask & ~personSkillsMask[i];
            if (reducedMask != currentMask) {
                long teamMask = calculateMinimumTeam(reducedMask) | (1L << i);
                if (memo[currentMask] == -1 || Long.bitCount(teamMask) <
                                                Long.bitCount(memo[currentMask])) {
                    memo[currentMask] = teamMask;
                }
            }
        }
        return memo[currentMask];
    }
    public int[] smallestSufficientTeam(String[] requiredSkills, List<List<String>> people) {
        totalPersons = people.size();
        int skillCount = requiredSkills.length;
        HashMap<String, Integer> skillIndexMap = new HashMap<String, Integer>();
        for (int i = 0; i < skillCount; i++) {
            skillIndexMap.put(requiredSkills[i], i);
        }
        personSkillsMask = new int[totalPersons];
        for (int i = 0; i < totalPersons; i++) {
            for (String skill : people.get(i)) {
                personSkillsMask[i] |= 1 << skillIndexMap.get(skill);
            }
        }
        memo = new long [1 << skillCount];
        Arrays.fill(memo, -1);
        long optimalMask = calculateMinimumTeam((1 << skillCount) - 1);
        int result[] = new int [Long.bitCount(optimalMask)];
        int index = 0;
        for (int i = 0; i < totalPersons; i++) {
            if (((optimalMask >> i) & 1) == 1) {
                result[index++] = i;
            }
        }
        return result;
    }
}

The code provided outlines a Java solution for the "Smallest Sufficient Team" problem, which is designed to identify the minimum number of persons required to cover a specified set of skills. It leverages bit manipulation to efficiently track and combine skills acquired through different team members.

  • Data structure setup:

    • Skills and people are mapped to integers for bit manipulation. Each person's skill is represented as a bitmask.
    • A memo array is used for dynamic programming to store intermediate results and avoid recomputation.
  • Core Function — calculateMinimumTeam(int currentMask):

    • This function recursively calculates the minimal bitmask representing the smallest team capable of achieving the skill set described by currentMask.
    • It operates by iterating through all persons and attempting to reduce currentMask by removing skills covered by each person, effectively finding combinations in a depth-first search manner.
  • Setting up the problem:

    • Maps each skill from requiredSkills array to unique integers using skillIndexMap.
    • Converts each person's list of skills into an integer mask and stores it in personSkillsMask.
  • Solving for the smallest team:

    • Initiates with a full mask ((1 << skillCount) - 1) representing all required skills.
    • Calls calculateMinimumTeam to find the optimal team combination stored as a long bitmask.
  • Interpreting the result:

    • Converts the bitmask representation of the optimal team back to an array of indices, representing the indices of persons in the smallest sufficient team.

This code effectively combines bitmasking and dynamic programming to solve the subset cover problem with an optimization twist, potentially handling large sets of skills and persons efficiently where direct computation would be impractical.

  • Python
python
class Solution:
    def minimumTeam(self, required_skills: List[str], team: List[List[str]]) -> List[int]:
        total_people = len(team)
        total_skills = len(required_skills)
        skill_to_index = {skill: idx for idx, skill in enumerate(required_skills)}
        person_skill_bits = [0] * total_people
        for idx in range(total_people):
            for skill in team[idx]:
                person_skill_bits[idx] |= 1 << skill_to_index[skill]
        subproblem = [-1] * (1 << total_skills)
        subproblem[0] = 0
    
        def calculate_min_team(current_skills):
            if subproblem[current_skills] != -1:
                return subproblem[current_skills]
            for person_index in range(total_people):
                updated_skills = current_skills & ~person_skill_bits[person_index]
                if updated_skills != current_skills:
                    team_mask = calculate_min_team(updated_skills) | (1 << person_index)
                    if (subproblem[current_skills] == -1 or
                        team_mask.bit_count() < subproblem[current_skills].bit_count()):
                        subproblem[current_skills] = team_mask
            return subproblem[current_skills]
            
        final_team_mask = calculate_min_team((1 << total_skills) - 1)
        final_team = []
        for idx in range(total_people):
            if (final_team_mask >> idx) & 1:
                final_team.append(idx)
        return final_team

The Python function minimumTeam in your code is designed to identify the smallest set of people from a given team who together have all the skills listed in required_skills.

The process uses bit manipulation to efficiently calculate combinations of team members and their associated skills. Every skill and every person is represented as a bit in integers. This bit-wise representation allows for rapid checks and combinations using binary operations.

  • Translate each skill into a unique index using a dictionary, skill_to_index.
  • Convert each person's skill set into an integer where each bit represents a skill they possess.
  • Use dynamic programming to store the minimum team needed to achieve each combination of skills in the subproblem array.
  • Base case: A team that offers no skills — represented as 0 — doesn't need anyone.
  • Recursively determine the minimal team needed by considering each person and checking if adding them to a smaller team improves the team's skill coverage without redundancies.
  • Count the bits to compare the size (number of members) of new possible teams versus existing teams stored in the dynamic programming table.
  • Once all combinations are evaluated, the correct combination representing all required skills is used to construct the final team.

The result is an array of indices corresponding to the members of the smallest team that covers all required skills. This approach ensures an optimal solution but remains computationally intensive, suitable primarily for scenarios with a constrained number of skills and team members due to its exponential nature in the worst case.

Comments

No comments yet.