Скоро Codeforces Round 1133 (Div. 1, Div. 2)! Регистрация здесь. ×
Word Ladder: For 1e5 Queries on a Static Dictionary
Разница между en1 и en2, 1624 символ(ов) изменены
# Word Ladder With 1e5 Queries — Efficient Approaches and Analysis↵
↵
Hello ,↵
↵
During learning graphs, I encountered a famous and classic problem 
_*Word Ladder_* problem. Given a beginWord and endWord, find the minimum number of single-character transformations, where each intermediate word must be from a given list. Given all words are of the same length. ↵
↵
This shows up on LeetCode, GFG, interviews, everywhere.↵
↵
But almost every version of the problem is single-query.
  ↵
You solve it once and move on.↵
↵
Now I want to discuss for a scenario where the dictionary is fixed and you need to answer queries in range of 
**1e5**.↵
↵
**N ≤ 5000 words**
  ↵
**L ≤ 10 (word length)**
  ↵
**Q ≤ 100000 queries**↵
↵
This completely changes the problem.↵
↵
The typical BFS solution (where we generate neighbours (26
\*L) at each stage and check whether they are in the dictionary or not and proceed) became far too slow.↵
↵
This article explores that space in depth and shows efficient techniques depending on the ratio of N vs Q.
  ↵
To understand the original classical problem better, you may refer to
 _:  ↵
https://www.geeksforgeeks.org/problems/word-ladder/1_↵
↵
↵
## Method 1: "Generate 26·L neighbors per BFS" (per query BFS)↵
---------------------------------------------------------------↵
↵
This is the classic solution.↵
↵
```cpp↵
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {↵
    unordered_set<string> dict(wordList.begin(), wordList.end());↵
    if (!dict.count(endWord)) return 0;↵
↵
    queue<pair<string,int>> q;↵
    q.push({beginWord, 1});↵
↵
    unordered_set<string> vis;↵
    vis.insert(beginWord);↵
↵
    while (!q.empty()) {↵
        auto [w, d] = q.front(); q.pop();↵
↵
        if (w == endWord) return d;↵
↵
        for (int i = 0; i < w.size(); i++) {↵
            char orig = w[i];↵
            for (char c = 'a'; c <= 'z'; c++) {↵
                if (c == orig) continue;↵
                w[i] = c;↵
                if (dict.count(w) && !vis.count(w)) {↵
                    vis.insert(w);↵
                    q.push({w, d + 1});↵
                }↵
            }↵
            w[i] = orig;↵
        }↵
    }↵
    return 0;↵
}↵
```↵
↵
Per query BFS:↵
↵
**O(N × 26 × L)** ≈ 5000 × 260 = **1.3 million opserations**↵
↵
↵
For **100k queries**:↵
↵
**1.3M × 100k = 1.3e11 operations    → TLE, impossible**↵
↵
↵
## 
Method 2: Pattern indexing (per-query BFS using ot/ht/ho*)↵
----------------------------------------------------------
* wildcards)↵
↵
```cpp↵
int ladder(string beginWord, string endWord, vector<string> wordList) {↵
    int L = beginWord.size();↵
    unordered_set<string> dict(wordList.begin(), wordList.end());↵
    if (!dict.count(endWord)) return 0;↵
↵
    unordered_map<string, vector<string>> buckets;↵
    for (auto &w : dict) {↵
        for (int i = 0; i < L; i++) {↵
            string p = w;↵
            p[i] = '*';↵
            buckets[p].push_back(w);↵
        }↵
    }↵
↵
    queue<pair<string,int>> q;↵
    unordered_set<string> vis;↵
↵
    q.push({beginWord, 1});↵
    vis.insert(beginWord);↵
↵
    while (!q.empty()) {↵
        auto [w, d] = q.front(); q.pop();↵
        if (w == endWord) return d;↵
↵
        for (int i = 0; i < L; i++) {↵
            string p = w;↵
            p[i] = '*';↵
↵
            for (auto &nei : buckets[p]) {↵
                if (!vis.count(nei)) {↵
                    vis.insert(nei);↵
                    q.push({nei, d+1});↵
                }↵
            }↵
            buckets[p].clear();  // important optimization↵
        }↵
    }↵
    return 0;↵
}↵
```↵
↵
Per query BFS:↵
↵
**O(N × L) = 5000 × 10 = 50k operations**↵
↵
↵
For **100k queries**:↵
↵
**50k × 100k = 5e9 → borderline slow, likely TLE in C++ under 1-2s**↵
↵
↵
## Method 3: Precomputed adjacency (O(N²·L) cost once) + BFS per query↵
-------------------------------------------------------------------↵
Solving this question, I first came up with a logic where words in the dictionary can be considered as nodes and those words having only one character different have an edge joining them. So in this way I developed an adjacency list but this came out to be O(N²·L) which was not good for the original problem but for this variation it is efficient.↵
↵
```cpp↵
bool differ1(const string &a, const string &b) {↵
    int c = 0;↵
    for (int i = 0; i < a.size(); i++)↵
        c += (a[i] != b[i]);↵
    return c == 1;↵
}↵
↵
vector<vector<int>> buildAdj(const vector<string>& dict) {↵
    int n = dict.size();↵
    vector<vector<int>> adj(n);↵
↵
    for (int i = 0; i < n; i++) {↵
        for (int j = i+1; j < n; j++) {↵
            if (differ1(dict[i], dict[j])) {↵
                adj[i].push_back(j);↵
                adj[j].push_back(i);↵
            }↵
        }↵
    }↵
    return adj;↵
}↵
↵
int bfsAdj(int start, int goal, const vector<vector<int>>& adj) {↵
    if (start == -1 || goal == -1) return 0;↵
    int n = adj.size();↵
↵
    vector<int> dist(n, -1);↵
    queue<int> q;↵
↵
    dist[start] = 1;↵
    q.push(start);↵
↵
    while (!q.empty()) {↵
        int u = q.front(); q.pop();↵
        if (u == goal) return dist[u];↵
↵
        for (int v : adj[u]) {↵
            if (dist[v] == -1) {↵
                dist[v] = dist[u] + 1;↵
                q.push(v);↵
            }↵
        }↵
    }↵
    return 0;↵
}↵
```↵
↵
#↵
## Analysis of the Three Main Approaches↵
↵
There are three reasonable strategies for solving multiple Word-Ladder queries when the dictionary is fixed:
  ↵
(1) On-the-fly neighbor generation,
  ↵
(2) pattern-indexed BFS, 
and ↵
(3) full adjacency precomputation.↵
↵
Each comes with different trade-offs depending on the number of queries Q compared to the dictionary size N.↵
↵
### 1. Neighbor Generation (26·L BFS)↵
↵
For each BFS step, we try replacinge every character in the word with all 26 letters.  ↵
This 
giveyields up to **26·L possible** neighbors and avoids anyrequires no preprocessing.↵
Its c
  ↵
C
omplexity per query is roughly **O(N·L)**, which is fastine for a single query, but becomes prohibitive when Q issmall Q but disastrous for large Q.↵
↵
For example, with **N = 5000 and **, **Q = 100000**, the total work crosses several billion operations, which isoperations become several billions → not rfealistic for standard time limitssible.↵
↵
### 2. Pattern Indexing (“ot”, “ht”, “ho”)*↵
This is the classic editorial solution:↵
you
↵
↵
We
 preprocess **N·L patterns, each mapping to the set of words that match that pattern.↵
Neighbor lookup becomes faster (L patterns + bucket lookups), but BFS still needs to examine
** wildcard patterns.  ↵
Lookup becomes faster, but BFS still touches
 a large porfraction of the dictionary for each query.↵
Its total cost is about O(N·L + Q·N·L); much better constants
.↵
↵
Total complexity: **O(N·L + Q·N·L)**.  ↵
Better
 than method 1, but still too slow once Q grows into the tens of thousandsfor large Q.↵
↵
### 3. Full Adjacency Precomputation (O(N²·L) once, BFS per query thereafter)↵
When the dictionary is static, the graph structure does not change.↵
We can p
↵
↵
P
recompute all edges by checking each pairs of dictionary words and connecting thoswords differing in one tchat differ in exactly one position.↵
This
racter.↵
↵
One-time
 preprocessing takes O(N²·L) time once, but every query afterward becomes a simple BFS on a ready-made adjacency list.↵
The per-query time drops to O(N), and the t
: **O(N²·L)**.  ↵
Then each query: simple BFS in **O(N)**.↵
↵
T
otal becomes plexity: **O(N²·L + Q·N), which is far more efficient than the previous method**.  ↵
Dominate
s when **Q is large (for example, when Q ≥ N)**.↵
This approach leverages amortization: the heavy preprocessing cost is paid once, and repeated queries become cheap.↵
↵
↵
## In short:↵
↵
- For **small Q**, method (1) or (2) is perfectly fine.↵
↵
fine.  ↵
- 
For **moderate Q**, pattern indexing scales better than neighbor generation.↵
↵
wins.  ↵
- 
For **large Q**, especially when **Q ≈ N or higher, **, **full adjacency precomputation is the superiorbest solution, as it minimizes repeated work across queries↵
↵
↵
↵
↵
↵
↵
↵
**.↵
↵

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en2 Английский Prakharg_8651 2025-12-02 09:55:01 1624
en1 Английский Prakharg_8651 2025-12-01 23:48:35 7489 Initial revision (published)