Synonymous Sentences

medium union find backtracking string

Problem

You are given a list of equivalent string pairs synonyms where synonyms[i] = [a, b] means a and b are synonyms, and a sentence text. Return all possible synonymous sentences sorted lexicographically. Synonymy is transitive: if a~b and b~c then a~c.

Inputsynonyms = [["happy","joy"],["sad","sorrow"],["joy","cheerful"]], text = "I am happy today but was sad yesterday"
Output["I am cheerful today but was sad yesterday", "I am cheerful today but was sorrow yesterday", "I am happy today but was sad yesterday", "I am happy today but was sorrow yesterday", "I am joy today but was sad yesterday", "I am joy today but was sorrow yesterday"]
happy/joy/cheerful form one group (3 choices) and sad/sorrow another (2 choices), so 3 × 2 = 6 sentences, each emitted in sorted order.

def generate_sentences(synonyms, text):
    parent = {}
    def find(x):
        parent.setdefault(x, x)
        while parent[x] != x:
            parent[x] = parent[parent[x]]
            x = parent[x]
        return x
    for a, b in synonyms:           # union all synonym pairs
        parent[find(a)] = find(b)
    groups = {}                     # root -> sorted choices
    for w in parent:
        groups.setdefault(find(w), []).append(w)
    for g in groups.values():
        g.sort()
    words = text.split()
    res = []
    def dfs(i, cur):
        if i == len(words):
            res.append(" ".join(cur))
            return
        w = words[i]
        root = parent.get(w)
        opts = groups[find(w)] if root is not None else [w]
        for choice in opts:         # try each synonym, then backtrack
            cur.append(choice)
            dfs(i + 1, cur)
            cur.pop()
    dfs(0, [])
    return res
function generateSentences(synonyms, text) {
  const parent = new Map();
  function find(x) {
    if (!parent.has(x)) parent.set(x, x);
    while (parent.get(x) !== x) {
      parent.set(x, parent.get(parent.get(x)));
      x = parent.get(x);
    }
    return x;
  }
  for (const [a, b] of synonyms) parent.set(find(a), find(b));
  const groups = new Map();
  for (const w of parent.keys()) {
    const r = find(w);
    if (!groups.has(r)) groups.set(r, []);
    groups.get(r).push(w);
  }
  for (const g of groups.values()) g.sort();
  const words = text.split(" ");
  const res = [];
  function dfs(i, cur) {
    if (i === words.length) { res.push(cur.join(" ")); return; }
    const w = words[i];
    const opts = parent.has(w) ? groups.get(find(w)) : [w];
    for (const choice of opts) {
      cur.push(choice);
      dfs(i + 1, cur);
      cur.pop();
    }
  }
  dfs(0, []);
  return res;
}
class Solution {
    Map<String, String> parent = new HashMap<>();
    String find(String x) {
        parent.putIfAbsent(x, x);
        while (!parent.get(x).equals(x)) {
            parent.put(x, parent.get(parent.get(x)));
            x = parent.get(x);
        }
        return x;
    }
    public List<String> generateSentences(List<List<String>> synonyms, String text) {
        for (List<String> p : synonyms) parent.put(find(p.get(0)), find(p.get(1)));
        Map<String, List<String>> groups = new HashMap<>();
        for (String w : parent.keySet())
            groups.computeIfAbsent(find(w), k -> new ArrayList<>()).add(w);
        for (List<String> g : groups.values()) Collections.sort(g);
        String[] words = text.split(" ");
        List<String> res = new ArrayList<>();
        dfs(0, words, new ArrayList<>(), groups, res);
        return res;
    }
    void dfs(int i, String[] words, List<String> cur,
             Map<String, List<String>> groups, List<String> res) {
        if (i == words.length) { res.add(String.join(" ", cur)); return; }
        List<String> opts = parent.containsKey(words[i])
            ? groups.get(find(words[i])) : List.of(words[i]);
        for (String c : opts) {
            cur.add(c);
            dfs(i + 1, words, cur, groups, res);
            cur.remove(cur.size() - 1);
        }
    }
}
class Solution {
    unordered_map<string, string> parent;
    string find(const string& key) {
        if (!parent.count(key)) parent[key] = key;
        string x = key;
        while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
        return x;
    }
public:
    vector<string> generateSentences(vector<vector<string>>& synonyms, string text) {
        for (auto& p : synonyms) parent[find(p[0])] = find(p[1]);
        unordered_map<string, vector<string>> groups;
        for (auto& kv : parent) groups[find(kv.first)].push_back(kv.first);
        for (auto& g : groups) sort(g.second.begin(), g.second.end());
        vector<string> words, res, cur;
        stringstream ss(text); string t;
        while (ss >> t) words.push_back(t);
        function<void(int)> dfs = [&](int i) {
            if (i == (int)words.size()) {
                string s; for (int k = 0; k < (int)cur.size(); k++) s += (k ? " " : "") + cur[k];
                res.push_back(s); return;
            }
            vector<string> opts = parent.count(words[i]) ? groups[find(words[i])] : vector<string>{words[i]};
            for (auto& c : opts) { cur.push_back(c); dfs(i + 1); cur.pop_back(); }
        };
        dfs(0);
        return res;
    }
};
Time: O(N · ∏kᵢ) Space: O(N · ∏kᵢ)