🧠
Sorting + Adjacent Comparison
💡 Sorting words groups similar prefixes together, so only adjacent words need to be compared, reducing comparisons drastically.
Intuition
After sorting, only neighbors can share prefixes. For each word, compare with previous and next word to find the minimum prefix length that differs.
Algorithm
- Sort the list of words lexicographically
- For each word, compare with previous and next word to find the longest common prefix length
- The shortest unique prefix length is one more than the max common prefix length with neighbors
- Return the prefixes of that length for all words
💡 Sorting reduces the problem to local checks, but careful indexing and prefix length calculation is needed.
def shortest_unique_prefix(words):
n = len(words)
sorted_words = sorted([(w, i) for i, w in enumerate(words)])
prefix_lengths = [0] * n
def common_prefix_len(a, b):
length = 0
for x, y in zip(a, b):
if x == y:
length += 1
else:
break
return length
for i in range(n):
prev_len = common_prefix_len(sorted_words[i][0], sorted_words[i-1][0]) if i > 0 else 0
next_len = common_prefix_len(sorted_words[i][0], sorted_words[i+1][0]) if i < n-1 else 0
prefix_lengths[sorted_words[i][1]] = max(prev_len, next_len) + 1
return [words[i][:prefix_lengths[i]] for i in range(n)]
# Driver code
if __name__ == '__main__':
words = ["zebra", "dog", "duck", "dove"]
print(shortest_unique_prefix(words))
Line Notes
sorted_words = sorted([(w, i) for i, w in enumerate(words)])Sort words with original indices to preserve order
def common_prefix_len(a, b):Helper to find length of common prefix between two words
prev_len = common_prefix_len(sorted_words[i][0], sorted_words[i-1][0])Compare with previous word if exists
prefix_lengths[sorted_words[i][1]] = max(prev_len, next_len) + 1Store prefix length at original index
import java.util.*;
public class ShortestUniquePrefix {
private static int commonPrefixLen(String a, String b) {
int len = 0;
int limit = Math.min(a.length(), b.length());
for (int i = 0; i < limit; i++) {
if (a.charAt(i) == b.charAt(i)) len++;
else break;
}
return len;
}
public static List<String> shortestUniquePrefix(List<String> words) {
int n = words.size();
List<Pair> sortedWords = new ArrayList<>();
for (int i = 0; i < n; i++) sortedWords.add(new Pair(words.get(i), i));
Collections.sort(sortedWords, Comparator.comparing(p -> p.word));
int[] prefixLengths = new int[n];
for (int i = 0; i < n; i++) {
int prevLen = i > 0 ? commonPrefixLen(sortedWords.get(i).word, sortedWords.get(i - 1).word) : 0;
int nextLen = i < n - 1 ? commonPrefixLen(sortedWords.get(i).word, sortedWords.get(i + 1).word) : 0;
prefixLengths[sortedWords.get(i).index] = Math.max(prevLen, nextLen) + 1;
}
List<String> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
String w = words.get(i);
int len = prefixLengths[i];
if (len > w.length()) len = w.length();
result.add(w.substring(0, len));
}
return result;
}
static class Pair {
String word;
int index;
Pair(String w, int i) { word = w; index = i; }
}
public static void main(String[] args) {
List<String> words = Arrays.asList("zebra", "dog", "duck", "dove");
System.out.println(shortestUniquePrefix(words));
}
}
Line Notes
Collections.sort(sortedWords, Comparator.comparing(p -> p.word));Sort words with original indices to preserve order
int prevLen = i > 0 ? commonPrefixLen(...) : 0;Find common prefix length with previous word
prefixLengths[sortedWords.get(i).index] = Math.max(prevLen, nextLen) + 1;Store prefix length at original index
result.add(w.substring(0, len));Add prefix substring to result preserving input order
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int commonPrefixLen(const string& a, const string& b) {
int len = 0;
int limit = min(a.size(), b.size());
for (int i = 0; i < limit; i++) {
if (a[i] == b[i]) len++;
else break;
}
return len;
}
vector<string> shortestUniquePrefix(const vector<string>& words) {
int n = words.size();
vector<pair<string, int>> sortedWords;
for (int i = 0; i < n; i++) sortedWords.emplace_back(words[i], i);
sort(sortedWords.begin(), sortedWords.end(), [](auto& a, auto& b) { return a.first < b.first; });
vector<int> prefixLengths(n);
for (int i = 0; i < n; i++) {
int prevLen = i > 0 ? commonPrefixLen(sortedWords[i].first, sortedWords[i - 1].first) : 0;
int nextLen = i < n - 1 ? commonPrefixLen(sortedWords[i].first, sortedWords[i + 1].first) : 0;
prefixLengths[sortedWords[i].second] = max(prevLen, nextLen) + 1;
}
vector<string> result(n);
for (int i = 0; i < n; i++) {
int len = prefixLengths[i];
if (len > (int)words[i].size()) len = words[i].size();
result[i] = words[i].substr(0, len);
}
return result;
}
int main() {
vector<string> words = {"zebra", "dog", "duck", "dove"};
vector<string> prefixes = shortestUniquePrefix(words);
for (auto& p : prefixes) cout << p << " ";
cout << endl;
return 0;
}
Line Notes
sort(sortedWords.begin(), sortedWords.end(), [](auto& a, auto& b) { return a.first < b.first; });Sort words with original indices to preserve order
int prevLen = i > 0 ? commonPrefixLen(...) : 0;Calculate common prefix length with previous word
prefixLengths[sortedWords[i].second] = max(prevLen, nextLen) + 1;Store prefix length at original index
result[i] = words[i].substr(0, len);Assign prefix substring preserving input order
function commonPrefixLen(a, b) {
let len = 0;
let limit = Math.min(a.length, b.length);
for (let i = 0; i < limit; i++) {
if (a[i] === b[i]) len++;
else break;
}
return len;
}
function shortestUniquePrefix(words) {
const sortedWords = words.map((w, i) => [w, i]).sort((a, b) => a[0].localeCompare(b[0]));
const n = sortedWords.length;
const prefixLengths = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
const prevLen = i > 0 ? commonPrefixLen(sortedWords[i][0], sortedWords[i - 1][0]) : 0;
const nextLen = i < n - 1 ? commonPrefixLen(sortedWords[i][0], sortedWords[i + 1][0]) : 0;
prefixLengths[sortedWords[i][1]] = Math.max(prevLen, nextLen) + 1;
}
return words.map((w, i) => w.substring(0, prefixLengths[i]));
}
// Test
const words = ["zebra", "dog", "duck", "dove"];
console.log(shortestUniquePrefix(words));
Line Notes
const sortedWords = words.map((w, i) => [w, i]).sort((a, b) => a[0].localeCompare(b[0]));Sort words with original indices to preserve order
const prevLen = i > 0 ? commonPrefixLen(...) : 0;Find common prefix length with previous word
prefixLengths[sortedWords[i][1]] = Math.max(prevLen, nextLen) + 1;Store prefix length at original index
return words.map((w, i) => w.substring(0, prefixLengths[i]));Return prefixes preserving input order
TimeO(n log n * m) where n is number of words and m is max word length
SpaceO(n * m) for sorting and output
Sorting takes O(n log n * m) due to string comparisons; prefix comparisons are O(n * m).
💡 For n=100000 and m=10, sorting is feasible and much faster than brute force.
Interview Verdict: Accepted / Efficient for large inputs
This approach is a practical improvement and often acceptable in interviews if tries are not required.