Shortest Word Distance II
Repeated distance queries over a fixed word list — preprocess word to sorted index list, two-pointer merge per query.
4 min read
design hash-map two-pointers
Problem#
Given a list of strings, support repeated shortest(word1, word2) queries returning the minimum index distance between any occurrence of the two words.
Examples#
["practice","makes","perfect","coding","makes"]; shortest("coding","practice")→3.shortest("makes","coding")→1.
Constraints#
1 <= wordsDict.length <= 3 * 10^4, up to5000queries.
Approach#
In the constructor, build unordered_map<string, vector<int>> of each word to its sorted occurrence indices. Each query two-pointer merges the two lists, tracking the minimum positive index gap.
Solution#
class WordDistance { unordered_map<string, vector<int>> pos;public: WordDistance(vector<string>& wordsDict) { for (int i = 0; i < (int)wordsDict.size(); ++i) pos[wordsDict[i]].push_back(i); } int shortest(string word1, string word2) { auto& a = pos[word1]; auto& b = pos[word2]; int i = 0, j = 0, ans = INT_MAX; while (i < (int)a.size() && j < (int)b.size()) { ans = min(ans, abs(a[i] - b[j])); if (a[i] < b[j]) ++i; else ++j; } return ans; }};import "math"
type WordDistance struct { pos map[string][]int}
func Constructor(wordsDict []string) WordDistance { pos := make(map[string][]int) for i, w := range wordsDict { pos[w] = append(pos[w], i) } return WordDistance{pos: pos}}
func (wd *WordDistance) Shortest(word1 string, word2 string) int { a := wd.pos[word1] b := wd.pos[word2] i, j, ans := 0, 0, math.MaxInt32 for i < len(a) && j < len(b) { diff := a[i] - b[j] if diff < 0 { diff = -diff } if diff < ans { ans = diff } if a[i] < b[j] { i++ } else { j++ } } return ans}from collections import defaultdictfrom typing import List
class WordDistance: def __init__(self, wordsDict: List[str]): self.pos = defaultdict(list) for i, w in enumerate(wordsDict): self.pos[w].append(i)
def shortest(self, word1: str, word2: str) -> int: a = self.pos[word1] b = self.pos[word2] i = j = 0 ans = float('inf') while i < len(a) and j < len(b): ans = min(ans, abs(a[i] - b[j])) if a[i] < b[j]: i += 1 else: j += 1 return ansclass WordDistance { constructor(wordsDict) { this.pos = new Map(); for (let i = 0; i < wordsDict.length; i++) { const w = wordsDict[i]; if (!this.pos.has(w)) this.pos.set(w, []); this.pos.get(w).push(i); } } shortest(word1, word2) { const a = this.pos.get(word1); const b = this.pos.get(word2); let i = 0, j = 0, ans = Infinity; while (i < a.length && j < b.length) { ans = Math.min(ans, Math.abs(a[i] - b[j])); if (a[i] < b[j]) i++; else j++; } return ans; }}class WordDistance { private Map<String, List<Integer>> pos;
public WordDistance(String[] wordsDict) { pos = new HashMap<>(); for (int i = 0; i < wordsDict.length; i++) { pos.computeIfAbsent(wordsDict[i], k -> new ArrayList<>()).add(i); } }
public int shortest(String word1, String word2) { List<Integer> a = pos.get(word1); List<Integer> b = pos.get(word2); int i = 0, j = 0, ans = Integer.MAX_VALUE; while (i < a.size() && j < b.size()) { ans = Math.min(ans, Math.abs(a.get(i) - b.get(j))); if (a.get(i) < b.get(j)) i++; else j++; } return ans; }}class WordDistance { private pos: Map<string, number[]>;
constructor(wordsDict: string[]) { this.pos = new Map(); for (let i = 0; i < wordsDict.length; i++) { const w = wordsDict[i]; if (!this.pos.has(w)) this.pos.set(w, []); this.pos.get(w)!.push(i); } }
shortest(word1: string, word2: string): number { const a = this.pos.get(word1)!; const b = this.pos.get(word2)!; let i = 0, j = 0, ans = Infinity; while (i < a.length && j < b.length) { ans = Math.min(ans, Math.abs(a[i] - b[j])); if (a[i] < b[j]) i++; else j++; } return ans; }}Editorial#
Because both index lists are sorted, advancing the smaller pointer is the optimal move — any further increment of the larger one will only widen the gap. Total work per query is linear in the sum of occurrence counts, far better than rescanning the whole array.
Complexity#
- Time:
O(n)init,O(a + b)per query. - Space:
O(n).
Concept revision#
Related#