LeetCode 1258 - Synonymous Sentences
This problem asks us to generate all possible synonymous variations of a given sentence based on a list of equivalent wo
Difficulty: 🟡 Medium
Topics: Array, Hash Table, String, Backtracking, Sort, Union-Find
Solution
Problem Understanding
This problem asks us to generate all possible synonymous variations of a given sentence based on a list of equivalent word pairs. Each synonym pair indicates that the two words can replace each other interchangeably. The output must include all possible sentences created by substituting words with their synonyms and must be sorted lexicographically.
The inputs are:
synonyms: a list of word pairs, where each pair[si, ti]indicatessiandtiare interchangeable. The constraints guarantee all pairs are unique and that the number of pairs is at most 10.text: a sentence with up to 10 words separated by single spaces. Each word may or may not have synonyms.
The output is a list of strings, representing all valid sentences after substituting words with their synonyms, sorted lexicographically.
Key constraints and observations:
- Because
synonyms.length <= 10andtexthas at most 10 words, the total number of possible sentences is relatively small and manageable for backtracking. - Synonym relationships can be transitive. For example, if "happy" = "joy" and "joy" = "cheerful", then "happy" = "cheerful".
- Words without synonyms remain unchanged.
- We must avoid duplicates and return sentences in lexicographic order.
Important edge cases:
- No synonyms: the sentence remains unchanged.
- Words in the sentence do not appear in
synonyms. - Synonym pairs form a chain longer than two words.
Approaches
A brute-force approach would be to check each word in the sentence and generate all possible substitutions directly based on the provided pairs. For each word, we could recursively try every pair match. While this would produce correct sentences, it fails to account for transitive synonyms and could create duplicate results that need deduplication. Additionally, as synonyms grow, the number of combinations can explode, leading to unnecessary repeated computation.
The optimal approach leverages the Union-Find (Disjoint Set Union) data structure to group all synonyms that are transitively connected. After forming synonym groups, we can precompute a mapping from each word to its complete set of synonyms. Then, using backtracking, we iterate over each word in the sentence, substituting it with every synonym in its group and generating all possible sentences. Finally, we sort the results lexicographically.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n * m) | O(2^n * m) | Recursively replace words using pairs; duplicates need removal |
| Optimal | O(n * 2^k * log(2^k)) | O(n + k) | Use Union-Find to group synonyms, then backtrack; sort results |
Here, n is the number of words in the sentence, and k is the number of words with synonyms.
Algorithm Walkthrough
- Initialize a Union-Find data structure. Each unique word from the synonyms list is a node.
- For each synonym pair
[si, ti], union the two words in the Union-Find structure, ensuring that all transitively connected synonyms share the same representative. - Build a mapping from the representative of each synonym group to the sorted list of words in that group. This allows quick lookup of all synonyms for any word.
- Split the input
textinto words. - Use backtracking to generate sentences. For each word in the sentence:
a. If it has synonyms in the mapping, iterate over each synonym, recursively generating sentences with that substitution.
b. If it has no synonyms, keep it unchanged and continue. 6. Collect all generated sentences into a list. 7. Sort the list lexicographically. 8. Return the sorted list.
Why it works: By using Union-Find, we correctly group all transitively connected synonyms. Backtracking ensures we explore every combination of synonymous replacements while preserving the original word order. Sorting at the end guarantees the lexicographical requirement.
Python Solution
from typing import List
from collections import defaultdict
class Solution:
def generateSentences(self, synonyms: List[List[str]], text: str) -> List[str]:
parent = {}
def find(word: str) -> str:
if word not in parent:
parent[word] = word
if parent[word] != word:
parent[word] = find(parent[word])
return parent[word]
def union(a: str, b: str):
pa, pb = find(a), find(b)
if pa != pb:
parent[pa] = pb
# Build union-find structure
for a, b in synonyms:
union(a, b)
# Build mapping from root to all synonyms
groups = defaultdict(list)
for word in parent:
root = find(word)
groups[root].append(word)
for key in groups:
groups[key].sort()
words = text.split()
res = []
def backtrack(index: int, path: List[str]):
if index == len(words):
res.append(' '.join(path))
return
word = words[index]
if word in parent:
for syn in groups[find(word)]:
backtrack(index + 1, path + [syn])
else:
backtrack(index + 1, path + [word])
backtrack(0, [])
return sorted(res)
The Python implementation first constructs the Union-Find structure and groups synonyms. Each word in the sentence is then replaced recursively by all synonyms from its group. Words without synonyms remain unchanged. Sorting ensures lexicographical order.
Go Solution
package main
import (
"sort"
"strings"
)
func generateSentences(synonyms [][]string, text string) []string {
parent := map[string]string{}
var find func(string) string
find = func(word string) string {
if _, exists := parent[word]; !exists {
parent[word] = word
}
if parent[word] != word {
parent[word] = find(parent[word])
}
return parent[word]
}
union := func(a, b string) {
pa, pb := find(a), find(b)
if pa != pb {
parent[pa] = pb
}
}
for _, pair := range synonyms {
union(pair[0], pair[1])
}
groups := map[string][]string{}
for word := range parent {
root := find(word)
groups[root] = append(groups[root], word)
}
for key := range groups {
sort.Strings(groups[key])
}
words := strings.Split(text, " ")
var res []string
var backtrack func(int, []string)
backtrack = func(index int, path []string) {
if index == len(words) {
res = append(res, strings.Join(path, " "))
return
}
word := words[index]
if _, exists := parent[word]; exists {
for _, syn := range groups[find(word)] {
backtrack(index+1, append(append([]string{}, path...), syn))
}
} else {
backtrack(index+1, append(path, word))
}
}
backtrack(0, []string{})
sort.Strings(res)
return res
}
The Go implementation mirrors Python but manages slices carefully to avoid modifying shared state during recursion. Maps handle Union-Find parents and groups. Lexicographic sorting is handled using sort.Strings.
Worked Examples
Example 1:
synonyms = [["happy","joy"],["sad","sorrow"],["joy","cheerful"]]
text = "I am happy today but was sad yesterday"
Step by step:
- Union-Find groups: "happy", "joy", "cheerful" are connected; "sad" and "sorrow" are connected.
- Groups mapping:
- root "happy": ["cheerful", "happy", "joy"]
- root "sad": ["sad", "sorrow"]
- Split sentence: ["I", "am", "happy", "today", "but", "was", "sad", "yesterday"]
- Backtracking:
- For "happy": try ["cheerful", "happy", "joy"]
- For "sad": try ["sad", "sorrow"]
- Combine all possibilities: 3 * 2 = 6 sentences
- Sort lexicographically
Final 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 works similarly but has fewer synonyms and only two resulting sentences.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * 2^k * log(2^k)) | Backtracking explores all combinations of words with synonyms; sorting adds log factor |
| Space | O(n + k) | Union-Find parent mapping and recursion stack for backtracking |
Here, n is the number of words in the sentence, k is the number of words with synonyms.
Test Cases
# Provided examples
assert Solution().generateSentences([["happy","joy"],["sad","sorrow"],["joy","cheerful"]], "I am happy today but was sad yesterday") == [
"I am cheerful today but was sad yesterday",
"I am cheerful today but