Mem: O(m^2 + m), Time: O(m*n + m^2) where m= number of users, n=number of languages
class Solution {
public:
// Returns true if user u and user v have a matching language.
// u and v are zero-based indexed users.
// languageSets[i] contains user i's languages.
bool areSpeaking(int u, int v, vector<vector<uint64_t>>& languageSets)
{
for(int ii=0; ii<languageSets[0].size(); ++ii)
{
if ( (languageSets[u][ii] & languageSets[v][ii]) != 0)
{
return true;
}
}
return false;
}
int minimumTeachings(int n, vector<vector<int>>& languages, vector<vector<int>>& friendships) {
int numUsers = languages.size();
int langBin = 50; // number of languages held in each number of languageSets[userNum].
int k = (n/langBin) +1;
// languageSets[i] contains user i's languages.
// Say there are 100 languages, then languagesSets[i] will be a vector of size 2.
// The first 50 languages will be kept in languageSets[i][0], the second 50 langugages
// will be kept in languagesSets[i][1].
// The numbers at languagesSets[i][x] record which languages are spoken.
// If user_i speaks languages 25 and 62, then languageSets[i][0] will be 2^25, and
// languageSets[i][1] will be 2^12. (Note 62%50 = 12.)
// The binary one in the number represents the language.
vector<vector<uint64_t>> languageSets(numUsers, vector<uint64_t>(k, 0));
// Populate languageSets
for(int user=0; user<numUsers; ++user)
{
for(int lang : languages[user])
{
int languageSet = lang / langBin;
uint64_t langIdx = lang % langBin;
uint64_t one = 1;
uint64_t shift = (one << langIdx);
languageSets[user][languageSet] = ((languageSets[user][languageSet]) | shift);
}
}
// nonSpeakingFriends keeps the set of non-speaking friends for each user.
// Not all nonSpeakingFriends are included. Only non-speaking freinds who are
// larger than user. u > v. This way, only one edge is in nonSpeakingFriends for
// each u-v friendship.
vector<unordered_set<int>> nonSpeakingFriends(numUsers, unordered_set<int>{});
for(int friendshipIdx=0; friendshipIdx<friendships.size(); ++friendshipIdx)
{
int u = (friendships[friendshipIdx][0] <= friendships[friendshipIdx][1]) ?
(friendships[friendshipIdx][0]) :
(friendships[friendshipIdx][1]);
int v = (friendships[friendshipIdx][0] <= friendships[friendshipIdx][1]) ?
(friendships[friendshipIdx][1]) :
(friendships[friendshipIdx][u]);
// Make friendship users zero-based indexed.
--u;
--v;
if (!areSpeaking(u, v, languageSets))
{
nonSpeakingFriends[u].insert(v);
}
}
// Answer. If a user has non-speaking friends, then the only way that user
// will ever talk to them is if they both know lang.
// So try each lang (only allowed to learn one language at a time).
int minUsersTaught = 1000;
for(int lang=1; lang<n+1; ++lang)
{
unordered_set<int> newSpeakers{};
int languageSet = lang / langBin;
uint64_t langIdx = lang % langBin;
for(int user=0; user<numUsers; ++user)
{
uint64_t one = 1;
uint64_t shift = (one << langIdx);
// if user doesn't know lang and user has non-speaking friends,
// then insert into newSpeakers_set.
if ( ((languageSets[user][languageSet] & shift) == 0) &&
(!nonSpeakingFriends[user].empty()))
{
newSpeakers.insert(user);
}
// Insert all non-speaking friends, who don't know lang, into newSpeakers as well.
for(int nonSpeakingFriend : nonSpeakingFriends[user])
{
if ( (languageSets[nonSpeakingFriend][languageSet] & shift) == 0)
{
newSpeakers.insert(nonSpeakingFriend);
}
}
}
// Compare number of new speakers for this lang to all other langs.
minUsersTaught = std::min(minUsersTaught, static_cast<int>(newSpeakers.size()));
}
return minUsersTaught;
}
};