Synonymous Sentences solution and explanation
5 more videos on this problem
1258 Synonymous Sentences (Biweekly Contest 13)Kelvin Chandra · 7:21 · 2K views
1258. Synonymous Sentences (LeetCode)hakunamatasq · 10:51 · 1K views
Leetcode 1258. Synonymous Sentences - Disjoint Set + BacktrackingCode-Yao · 17:40 · 844 views
1258. Synonymous Sentences(disjoint sets and backtracking)Ankur Kothari · 1:34:26 · 393 views
Synonymous SentencesOwen Wu · 24:16 · 42 views
LeetCode 1258: Synonymous SentencesAlitaCode · 31:58 · 10 views
The problem
You are given a list of equivalent string pairs synonyms where synonyms[i] = [si, ti] indicates that si and ti are equivalent strings. You are also given a sentence text.
Return all possible synonymous sentences sorted lexicographically.
Example 1:
Input: synonyms = [["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"]
Example 2:
Input: synonyms = [["happy","joy"],["cheerful","glad"]], text = "I am happy today but was sad yesterday" Output: ["I am happy today but was sad yesterday","I am joy today but was sad yesterday"]
Constraints:
0 <= synonyms.length <= 10synonyms[i].length == 21 <= si.length, ti.length <= 10si != titextconsists of at most10words.- All the pairs of
synonymsare unique. - The words of
textare separated by single spaces.
Approach overview
Problem Overview: You are given pairs of synonymous words and a sentence. Replace words with their synonyms to generate every possible sentence variation. The final output must contain all valid sentences sorted lexicographically.
Approach 1: Graph + Backtracking (Time: O(G + S * K^L), Space: O(G + L))
Model synonyms as an undirected graph using a hash table. Each word is a node and each synonym pair forms an edge. Traverse the graph with DFS or BFS to collect all words connected to a given synonym group. While processing the sentence, if a word appears in the graph, replace it with every synonym in its connected component and recursively build sentences using backtracking. This approach directly explores all combinations but repeatedly traverses synonym groups unless cached.
Approach 2: Union-Find + DFS (Time: O(P α(P) + C log C), Space: O(P + C))
Use Union-Find to group synonymous words efficiently. Each pair is unioned so all related words share the same root. After building groups, map each root to a sorted list of synonyms. While constructing sentences, iterate through the input words: if a word belongs to a synonym group, branch into every candidate word from that group and continue recursively using DFS. Sorting each group ensures the final results are generated in lexicographic order without an additional global sort. Union-Find avoids repeated graph traversal and handles large synonym networks efficiently.
Recommended for interviews: Union-Find + DFS is the expected solution. It separates two concerns cleanly: grouping synonyms with Union-Find and generating combinations with DFS. A basic graph + backtracking approach shows you understand the synonym connectivity, but the Union-Find version demonstrates stronger algorithmic design and scales better when the synonym list grows.
Solution
We can notice that the synonyms in the problem are transitive, i.e., if a and b are synonyms, and b and c are synonyms, then a and c are also synonyms. Therefore, we can use a union-find set to find the connected components of synonyms, where all the words in each connected component are synonyms and are sorted in lexicographical order.
Next, we split the string text into a word array sentence by spaces. For each word sentence[i], if it is a synonym, we replace it with all the words in the connected component, otherwise, we do not replace it. In this way, we can get all the sentences. This can be implemented by DFS search.
We design a function dfs(i), which represents starting from the ith word of sentence, replacing it with all the words in the connected component, and then recursively processing the following words.
If i is greater than or equal to the length of sentence, it means that we have processed all the words, and at this time, we add the current sentence to the answer array. Otherwise, if sentence[i] is not a synonym, we do not replace it, directly add it to the current sentence, and then recursively process the following words. Otherwise, we replace sentence[i] with all the words in the connected component, and also recursively process the following words.
Finally, return the answer array.
The time complexity is O(n^2), and the space complexity is O(n). Where n is the number of words.
class UnionFind:
def __init__(self, n):
self.p = list(range(n))
self.size = [1] * n
def find(self, x):
if self.p[x] != x:
self.p[x] = self.find(self.p[x])
return self.p[x]
def union(self, a, b):
pa, pb = self.find(a), self.find(b)
if pa != pb:
if self.size[pa] > self.size[pb]:
self.p[pb] = pa
self.size[pa] += self.size[pb]
else:
self.p[pa] = pb
self.size[pb] += self.size[pa]
class Solution:
def generateSentences(self, synonyms: List[List[str]], text: str) -> List[str]:
def dfs(i):
if i >= len(sentence):
ans.append(' '.join(t))
return
if sentence[i] not in d:
t.append(sentence[i])
dfs(i + 1)
t.pop()
else:
root = uf.find(d[sentence[i]])
for j in g[root]:
t.append(words[j])
dfs(i + 1)
t.pop()
words = list(set(chain.from_iterable(synonyms)))
d = {w: i for i, w in enumerate(words)}
uf = UnionFind(len(d))
for a, b in synonyms:
uf.union(d[a], d[b])
g = defaultdict(list)
for i in range(len(words)):
g[uf.find(i)].append(i)
for k in g.keys():
g[k].sort(key=lambda i: words[i])
sentence = text.split()
ans = []
t = []
dfs(0)
return ansclass UnionFind {
private int[] p;
private int[] size;
public UnionFind(int n) {
p = new int[n];
size = new int[n];
for (int i = 0; i < n; ++i) {
p[i] = i;
size[i] = 1;
}
}
public int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
public void union(int a, int b) {
int pa = find(a), pb = find(b);
if (pa != pb) {
if (size[pa] > size[pb]) {
p[pb] = pa;
size[pa] += size[pb];
} else {
p[pa] = pb;
size[pb] += size[pa];
}
}
}
}
class Solution {
private List<String> ans = new ArrayList<>();
private List<String> t = new ArrayList<>();
private List<String> words;
private Map<String, Integer> d;
private UnionFind uf;
private List<Integer>[] g;
private String[] sentence;
public List<String> generateSentences(List<List<String>> synonyms, String text) {
Set<String> ss = new HashSet<>();
for (List<String> pairs : synonyms) {
ss.addAll(pairs);
}
words = new ArrayList<>(ss);
int n = words.size();
d = new HashMap<>(n);
for (int i = 0; i < words.size(); ++i) {
d.put(words.get(i), i);
}
uf = new UnionFind(n);
for (List<String> pairs : synonyms) {
uf.union(d.get(pairs.get(0)), d.get(pairs.get(1)));
}
g = new List[n];
Arrays.setAll(g, k -> new ArrayList<>());
for (int i = 0; i < n; ++i) {
g[uf.find(i)].add(i);
}
for (List<Integer> e : g) {
e.sort((i, j) -> words.get(i).compareTo(words.get(j)));
}
sentence = text.split(" ");
dfs(0);
return ans;
}
private void dfs(int i) {
if (i >= sentence.length) {
ans.add(String.join(" ", t));
return;
}
if (!d.containsKey(sentence[i])) {
t.add(sentence[i]);
dfs(i + 1);
t.remove(t.size() - 1);
} else {
for (int j : g[uf.find(d.get(sentence[i]))]) {
t.add(words.get(j));
dfs(i + 1);
t.remove(t.size() - 1);
}
}
}
}class UnionFind {
public:
UnionFind(int n) {
p = vector<int>(n);
size = vector<int>(n, 1);
iota(p.begin(), p.end(), 0);
}
void unite(int a, int b) {
int pa = find(a), pb = find(b);
if (pa != pb) {
if (size[pa] > size[pb]) {
p[pb] = pa;
size[pa] += size[pb];
} else {
p[pa] = pb;
size[pb] += size[pa];
}
}
}
int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
private:
vector<int> p, size;
};
class Solution {
public:
vector<string> generateSentences(vector<vector<string>>& synonyms, string text) {
unordered_set<string> ss;
for (auto& pairs : synonyms) {
ss.insert(pairs[0]);
ss.insert(pairs[1]);
}
vector<string> words{ss.begin(), ss.end()};
unordered_map<string, int> d;
int n = words.size();
for (int i = 0; i < n; ++i) {
d[words[i]] = i;
}
UnionFind uf(n);
for (auto& pairs : synonyms) {
uf.unite(d[pairs[0]], d[pairs[1]]);
}
vector<vector<int>> g(n);
for (int i = 0; i < n; ++i) {
g[uf.find(i)].push_back(i);
}
for (int i = 0; i < n; ++i) {
sort(g[i].begin(), g[i].end(), [&](int a, int b) {
return words[a] < words[b];
});
}
vector<string> sentence;
string s;
istringstream iss(text);
while (iss >> s) {
sentence.emplace_back(s);
}
vector<string> ans;
vector<string> t;
function<void(int)> dfs = [&](int i) {
if (i >= sentence.size()) {
string s;
for (int j = 0; j < t.size(); ++j) {
if (j) {
s += " ";
}
s += t[j];
}
ans.emplace_back(s);
return;
}
if (!d.count(sentence[i])) {
t.emplace_back(sentence[i]);
dfs(i + 1);
t.pop_back();
} else {
for (int j : g[uf.find(d[sentence[i]])]) {
t.emplace_back(words[j]);
dfs(i + 1);
t.pop_back();
}
}
};
dfs(0);
return ans;
}
};type unionFind struct {
p, size []int
}
func newUnionFind(n int) *unionFind {
p := make([]int, n)
size := make([]int, n)
for i := range p {
p[i] = i
size[i] = 1
}
return &unionFind{p, size}
}
func (uf *unionFind) find(x int) int {
if uf.p[x] != x {
uf.p[x] = uf.find(uf.p[x])
}
return uf.p[x]
}
func (uf *unionFind) union(a, b int) {
pa, pb := uf.find(a), uf.find(b)
if pa != pb {
if uf.size[pa] > uf.size[pb] {
uf.p[pb] = pa
uf.size[pa] += uf.size[pb]
} else {
uf.p[pa] = pb
uf.size[pb] += uf.size[pa]
}
}
}
func generateSentences(synonyms [][]string, text string) (ans []string) {
ss := map[string]bool{}
for _, pairs := range synonyms {
ss[pairs[0]] = true
ss[pairs[1]] = true
}
words := []string{}
for w := range ss {
words = append(words, w)
}
d := map[string]int{}
for i, w := range words {
d[w] = i
}
uf := newUnionFind(len(words))
for _, pairs := range synonyms {
uf.union(d[pairs[0]], d[pairs[1]])
}
g := make([][]int, len(words))
for i := range g {
g[uf.find(i)] = append(g[uf.find(i)], i)
}
for i := range g {
sort.Slice(g[i], func(a, b int) bool { return words[g[i][a]] < words[g[i][b]] })
}
t := []string{}
sentences := strings.Split(text, " ")
var dfs func(int)
dfs = func(i int) {
if i >= len(sentences) {
ans = append(ans, strings.Join(t, " "))
return
}
if _, ok := ss[sentences[i]]; !ok {
t = append(t, sentences[i])
dfs(i + 1)
t = t[:len(t)-1]
return
} else {
for _, j := range g[uf.find(d[sentences[i]])] {
t = append(t, words[j])
dfs(i + 1)
t = t[:len(t)-1]
}
}
}
dfs(0)
return
}Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Graph Traversal + Backtracking | O(G + S * K^L) | O(G + L) | Useful when modeling synonym relationships directly as a graph and the synonym network is small. |
| Union-Find + DFS | O(P α(P) + C log C) | O(P + C) | Best general solution. Efficiently groups synonyms and generates combinations in lexicographic order. |
Frequently asked questions
Is Synonymous Sentences easy or hard?
Synonymous Sentences is usually classified as a medium difficulty problem. The challenge comes from combining two ideas: grouping words using Union-Find and generating all sentence permutations using DFS or backtracking.
Synonymous Sentences Python/Java solution
A typical implementation builds a Union-Find structure for all synonym pairs, maps each root to a sorted list of words, and then runs DFS over the sentence. Python, Java, C++, and Go implementations follow the same pattern: union operations for grouping and recursive generation for sentence combinations.
How to solve Synonymous Sentences in O(n)?
Building synonym groups with Union-Find is close to linear time because each union and find operation runs in nearly O(1) amortized time. After grouping, DFS or backtracking generates sentences by iterating through synonym lists for each word position. The combination generation dominates the runtime when many substitutions exist.
What is the best approach for Synonymous Sentences?
Union-Find combined with DFS is the most efficient approach. Union-Find groups all related synonyms into connected components in near O(1) amortized time per union operation. DFS or backtracking then generates every valid sentence combination by replacing words with others from the same synonym group.
Is Synonymous Sentences asked at Google/Amazon/Meta?
Variants of synonym grouping and sentence generation appear in interviews at companies like Google, Amazon, and Meta. The problem tests knowledge of Union-Find, graph connectivity, and recursive backtracking for generating combinations.
What data structure is used in Synonymous Sentences?
Union-Find (Disjoint Set Union) is commonly used to group synonyms efficiently. A hash map stores the mapping from group root to all words in that synonym set, and DFS/backtracking generates the possible sentences. Arrays or lists store the final combinations.
What is the time complexity of Synonymous Sentences?
The Union-Find setup takes roughly O(P α(P)) where P is the number of synonym pairs and α is the inverse Ackermann function. Generating sentences depends on the number of combinations, which can grow exponentially with the number of replaceable words. Sorting synonym groups typically adds O(C log C).
