2306. Naming a Company
Problem Description
You have an array of strings called ideas
that contains potential names for a company. To create a company name, you need to follow these steps:
- Pick two different names from the
ideas
array - let's call themideaA
andideaB
. - Swap the first letters of these two names with each other. For example, if
ideaA = "coffee"
andideaB = "donuts"
, after swapping first letters you get"doffee"
and"conuts"
. - Check if both newly formed words are NOT in the original
ideas
array. If both new words don't exist inideas
, then you can create a valid company name by concatenating them with a space:"doffee conuts"
. - If either of the new words already exists in
ideas
, this combination doesn't form a valid company name.
Your task is to count how many distinct valid company names can be formed using this process.
For example, if ideas = ["coffee", "donuts", "time", "toffee"]
:
- Swapping first letters of
"coffee"
and"donuts"
gives"doffee"
and"conuts"
. Since neither exists inideas
,"doffee conuts"
is valid. - Swapping first letters of
"coffee"
and"toffee"
gives"toffee"
and"coffee"
. Both exist inideas
, so this is NOT valid. - Swapping first letters of
"donuts"
and"coffee"
gives"conuts"
and"doffee"
. Since neither exists inideas
,"conuts doffee"
is valid (this is different from"doffee conuts"
).
The function should return the total count of all possible distinct valid company names.
Intuition
The brute force approach would be to try all pairs of ideas and check if swapping their first letters creates two new valid words. However, with potentially thousands of ideas, checking all pairs would be inefficient.
Let's think about what makes a valid pair. For two words ideaA
and ideaB
to form a valid company name after swapping first letters:
- When we replace
ideaA
's first letter withideaB
's first letter, the resulting word shouldn't exist inideas
- When we replace
ideaB
's first letter withideaA
's first letter, the resulting word shouldn't exist inideas
The key insight is that we can precompute this information. Since there are only 26 possible first letters (a-z), we can create a 2D array f[i][j]
where:
i
represents the original first letter (0-25 for a-z)j
represents the replacement first letter (0-25 for a-z)f[i][j]
counts how many words starting with letteri
can have their first letter replaced with letterj
without creating a word that exists inideas
Once we have this precomputed table, we can efficiently count valid pairs. For each word in ideas
, we check which letters it can swap with (creating a non-existing word). For each such valid replacement letter j
, we add f[j][i]
to our answer. This counts all the words that start with letter j
and can swap with our current word's first letter i
to form valid pairs.
This approach avoids checking every pair explicitly. Instead, we leverage the fact that the validity of a swap depends only on the first letters and whether the resulting words exist in our set, allowing us to group and count efficiently based on first letters.
Solution Approach
We implement the solution using a counting approach with preprocessing:
Step 1: Initialize Data Structures
- Create a hash set
s
containing all ideas for O(1) lookup - Create a 2D array
f[26][26]
initialized to zeros, wheref[i][j]
will store the count of words starting with letteri
that can be replaced with letterj
without creating an existing word
Step 2: Build the Frequency Table
For each word v
in ideas
:
- Get the index
i
of its first letter:i = ord(v[0]) - ord('a')
- Convert the word to a list for easy character manipulation
- Try replacing the first letter with each of the 26 possible letters
j
:- Create a new word by setting
t[0] = chr(ord('a') + j)
- If this new word is NOT in the set
s
, incrementf[i][j] += 1
- Create a new word by setting
This preprocessing tells us: for words starting with letter i
, how many can have their first letter replaced with letter j
without creating an existing word.
Step 3: Count Valid Pairs
Initialize ans = 0
to store the final count. For each word v
in ideas
:
- Get the index
i
of its first letter - For each possible replacement letter
j
:- Create a new word by replacing
v
's first letter with letterj
- If this new word is NOT in set
s
:- Add
f[j][i]
to the answer - This counts all words that start with letter
j
and can swap with our current word (which starts with letteri
) to form valid pairs
- Add
- Create a new word by replacing
Why This Works:
When we find that word v
(starting with letter i
) can have its first letter replaced with j
without creating an existing word, we look up f[j][i]
. This value tells us how many words starting with letter j
can have their first letter replaced with i
without creating an existing word. Each such word forms a valid pair with v
because:
- Swapping
v
's first letteri
withj
creates a non-existing word - Swapping that other word's first letter
j
withi
creates a non-existing word
The algorithm runs in O(n × 26) time where n is the number of ideas, as we iterate through all ideas twice, each time checking 26 possible letter replacements.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with ideas = ["coffee", "donuts", "time", "toffee"]
.
Step 1: Initialize Data Structures
- Create set
s = {"coffee", "donuts", "time", "toffee"}
for O(1) lookups - Create 2D array
f[26][26]
initialized with zeros
Step 2: Build the Frequency Table
Process each word to fill f[i][j]
:
For "coffee" (starts with 'c', index 2):
- Try replacing 'c' with each letter a-z
- "aoffee" ✓ not in s, so f[2][0]++ → f[2][0] = 1
- "boffee" ✓ not in s, so f[2][1]++ → f[2][1] = 1
- "coffee" ✗ exists in s, skip
- "doffee" ✓ not in s, so f[2][3]++ → f[2][3] = 1
- ... (other letters also don't exist, incrementing respective counters)
For "donuts" (starts with 'd', index 3):
- "aonuts" ✓ not in s, so f[3][0]++ → f[3][0] = 1
- "bonuts" ✓ not in s, so f[3][1]++ → f[3][1] = 1
- "conuts" ✓ not in s, so f[3][2]++ → f[3][2] = 1
- "donuts" ✗ exists in s, skip
- ... (continuing for all letters)
For "time" (starts with 't', index 19):
- Most replacements create non-existing words except "time" itself
- Updates f[19][j] for valid replacements
For "toffee" (starts with 't', index 19):
- "aoffee" ✓ not in s, so f[19][0]++ → f[19][0] = 2
- "boffee" ✓ not in s, so f[19][1]++ → f[19][1] = 2
- "coffee" ✗ exists in s, skip (f[19][2] stays at current value)
- "doffee" ✓ not in s, so f[19][3]++ → f[19][3] = 2
- ... (continuing for all letters)
Step 3: Count Valid Pairs
Now count valid pairs by checking each word:
For "coffee" (starts with 'c', index 2):
- Try replacing 'c' with each letter:
- 'c' → 'd': "doffee" ✓ not in s
- Add f[3][2] = 1 (one word starting with 'd' can swap with 'c')
- This counts the pair (coffee, donuts)
- 'c' → 't': "toffee" ✗ exists in s, skip
- Other valid replacements have f[j][2] = 0, so add nothing
For "donuts" (starts with 'd', index 3):
- 'd' → 'c': "conuts" ✓ not in s
- Add f[2][3] = 1 (one word starting with 'c' can swap with 'd')
- This counts the pair (donuts, coffee)
- Other valid replacements have f[j][3] = 0
For "time" (starts with 't', index 19):
- 't' → 'c': "cime" ✓ not in s
- Add f[2][19] = 0 (no words starting with 'c' can swap with 't')
- 't' → 'd': "dime" ✓ not in s
- Add f[3][19] = 0 (no words starting with 'd' can swap with 't')
- All valid replacements have f[j][19] = 0
For "toffee" (starts with 't', index 19):
- 't' → 'c': "coffee" ✗ exists in s, skip
- 't' → 'd': "doffee" ✓ not in s
- Add f[3][19] = 0
- All valid replacements have f[j][19] = 0
Final Answer: 2 We found 2 valid company names:
- "doffee conuts" (from swapping coffee and donuts)
- "conuts doffee" (from swapping donuts and coffee)
Solution Implementation
1class Solution:
2 def distinctNames(self, ideas: List[str]) -> int:
3 # Convert ideas list to set for O(1) lookup
4 ideas_set = set(ideas)
5
6 # Create a 26x26 matrix to store counts
7 # count_matrix[i][j] represents how many valid swaps exist
8 # when changing from letter i to letter j
9 count_matrix = [[0] * 26 for _ in range(26)]
10
11 # First pass: For each idea, count valid letter substitutions
12 for idea in ideas:
13 # Get the index of the first letter (0-25 for a-z)
14 original_letter_idx = ord(idea[0]) - ord('a')
15
16 # Convert string to list for character manipulation
17 char_list = list(idea)
18
19 # Try replacing first letter with each letter a-z
20 for new_letter_idx in range(26):
21 char_list[0] = chr(ord('a') + new_letter_idx)
22 new_word = ''.join(char_list)
23
24 # If the new word doesn't exist in original ideas, it's valid
25 if new_word not in ideas_set:
26 count_matrix[original_letter_idx][new_letter_idx] += 1
27
28 # Second pass: Count total valid pairs
29 total_pairs = 0
30 for idea in ideas:
31 # Get the index of the first letter
32 original_letter_idx = ord(idea[0]) - ord('a')
33
34 # Convert string to list for character manipulation
35 char_list = list(idea)
36
37 # For each valid substitution of current idea
38 for new_letter_idx in range(26):
39 char_list[0] = chr(ord('a') + new_letter_idx)
40 new_word = ''.join(char_list)
41
42 # If this substitution is valid (doesn't create existing idea)
43 if new_word not in ideas_set:
44 # Add count of valid pairs where the other idea starts with
45 # new_letter_idx and can be swapped to original_letter_idx
46 total_pairs += count_matrix[new_letter_idx][original_letter_idx]
47
48 return total_pairs
49
1class Solution {
2 public long distinctNames(String[] ideas) {
3 // Store all original ideas in a HashSet for O(1) lookup
4 Set<String> originalIdeas = new HashSet<>();
5 for (String idea : ideas) {
6 originalIdeas.add(idea);
7 }
8
9 // validSwapCount[i][j] represents how many ideas starting with character 'i'
10 // can have their first character swapped to 'j' without creating a conflict
11 int[][] validSwapCount = new int[26][26];
12
13 // For each idea, count valid swaps from its first character to all other characters
14 for (String idea : ideas) {
15 char[] ideaChars = idea.toCharArray();
16 int originalFirstCharIndex = ideaChars[0] - 'a';
17
18 // Try swapping to each possible character (a-z)
19 for (int newCharIndex = 0; newCharIndex < 26; ++newCharIndex) {
20 ideaChars[0] = (char) (newCharIndex + 'a');
21 String swappedIdea = String.valueOf(ideaChars);
22
23 // If the swapped version doesn't exist in original set, it's a valid swap
24 if (!originalIdeas.contains(swappedIdea)) {
25 ++validSwapCount[originalFirstCharIndex][newCharIndex];
26 }
27 }
28 }
29
30 long totalDistinctPairs = 0;
31
32 // For each idea, count how many other ideas it can form valid pairs with
33 for (String idea : ideas) {
34 char[] ideaChars = idea.toCharArray();
35 int originalFirstCharIndex = ideaChars[0] - 'a';
36
37 // Try swapping current idea's first character to each possible character
38 for (int newCharIndex = 0; newCharIndex < 26; ++newCharIndex) {
39 ideaChars[0] = (char) (newCharIndex + 'a');
40 String swappedIdea = String.valueOf(ideaChars);
41
42 // If this swap is valid for current idea
43 if (!originalIdeas.contains(swappedIdea)) {
44 // Add count of ideas that start with newCharIndex and can swap to originalFirstCharIndex
45 // This forms valid pairs where both swaps don't create conflicts
46 totalDistinctPairs += validSwapCount[newCharIndex][originalFirstCharIndex];
47 }
48 }
49 }
50
51 return totalDistinctPairs;
52 }
53}
54
1class Solution {
2public:
3 long long distinctNames(vector<string>& ideas) {
4 // Store all original ideas in a set for O(1) lookup
5 unordered_set<string> originalIdeas(ideas.begin(), ideas.end());
6
7 // validSwaps[i][j] counts how many ideas starting with char 'i'
8 // can have their first letter changed to char 'j' without creating
9 // an existing idea
10 int validSwaps[26][26]{};
11
12 // For each idea, count valid character swaps
13 for (auto idea : ideas) {
14 int originalFirstChar = idea[0] - 'a';
15
16 // Try replacing the first character with each letter a-z
17 for (int newChar = 0; newChar < 26; ++newChar) {
18 idea[0] = newChar + 'a';
19
20 // If the modified idea doesn't exist in the original set,
21 // this is a valid swap
22 if (!originalIdeas.count(idea)) {
23 ++validSwaps[originalFirstChar][newChar];
24 }
25 }
26 }
27
28 long long totalDistinctPairs = 0;
29
30 // For each idea, find all valid pairs it can form
31 for (auto& idea : ideas) {
32 int originalFirstChar = idea[0] - 'a';
33
34 // Try swapping with each possible character
35 for (int newChar = 0; newChar < 26; ++newChar) {
36 idea[0] = newChar + 'a';
37
38 // If this idea with swapped first char doesn't exist,
39 // count all ideas that start with newChar and can be
40 // swapped to originalFirstChar
41 if (!originalIdeas.count(idea)) {
42 totalDistinctPairs += validSwaps[newChar][originalFirstChar];
43 }
44 }
45 }
46
47 return totalDistinctPairs;
48 }
49};
50
1function distinctNames(ideas: string[]): number {
2 // Store all original ideas in a set for O(1) lookup
3 const originalIdeas = new Set<string>(ideas);
4
5 // validSwaps[i][j] counts how many ideas starting with char 'i'
6 // can have their first letter changed to char 'j' without creating
7 // an existing idea
8 const validSwaps: number[][] = Array(26).fill(null).map(() => Array(26).fill(0));
9
10 // For each idea, count valid character swaps
11 for (const idea of ideas) {
12 const originalFirstChar = idea.charCodeAt(0) - 'a'.charCodeAt(0);
13
14 // Try replacing the first character with each letter a-z
15 for (let newChar = 0; newChar < 26; newChar++) {
16 const modifiedIdea = String.fromCharCode(newChar + 'a'.charCodeAt(0)) + idea.substring(1);
17
18 // If the modified idea doesn't exist in the original set,
19 // this is a valid swap
20 if (!originalIdeas.has(modifiedIdea)) {
21 validSwaps[originalFirstChar][newChar]++;
22 }
23 }
24 }
25
26 let totalDistinctPairs = 0;
27
28 // For each idea, find all valid pairs it can form
29 for (const idea of ideas) {
30 const originalFirstChar = idea.charCodeAt(0) - 'a'.charCodeAt(0);
31
32 // Try swapping with each possible character
33 for (let newChar = 0; newChar < 26; newChar++) {
34 const modifiedIdea = String.fromCharCode(newChar + 'a'.charCodeAt(0)) + idea.substring(1);
35
36 // If this idea with swapped first char doesn't exist,
37 // count all ideas that start with newChar and can be
38 // swapped to originalFirstChar
39 if (!originalIdeas.has(modifiedIdea)) {
40 totalDistinctPairs += validSwaps[newChar][originalFirstChar];
41 }
42 }
43 }
44
45 return totalDistinctPairs;
46}
47
Time and Space Complexity
The time complexity is O(n × m × |Σ|)
, where:
n
is the number of strings in theideas
listm
is the maximum length of the strings (used when creating new strings with''.join(t)
)|Σ|
is the size of the character set (26 letters in this problem)
The algorithm has two main loops that iterate through all ideas
:
- First loop: For each idea (
n
iterations), it checks 26 possible first letter replacements (|Σ|
iterations), and for each replacement, it creates a new string using''.join(t)
which takesO(m)
time. The set lookupnot in s
isO(m)
in the worst case due to string hashing. - Second loop: Similar structure with
n × |Σ| × m
operations.
Overall: O(n × |Σ| × m) + O(n × |Σ| × m) = O(n × m × |Σ|)
The space complexity is O(|Σ|²)
, where:
- The 2D array
f
has dimensions26 × 26
, which isO(|Σ|²)
- The set
s
storesn
strings, each of length up tom
, which isO(n × m)
- Temporary variables like list
t
useO(m)
space
Since |Σ|² = 26² = 676
is constant and typically smaller than n × m
for large inputs, the dominant space factor depends on the input size. However, following the reference answer's notation, the auxiliary space complexity excluding the input storage is O(|Σ|²)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Double Counting Valid Pairs
A common mistake is thinking that if word A can swap with word B to form a valid pair, we might accidentally count this pair twice (once when processing A, once when processing B). However, the algorithm actually handles this correctly by design - it counts each ordered pair (A, B) exactly once, which is what we want since "doffee conuts" and "conuts doffee" are considered different company names.
2. String Manipulation Inefficiency
Creating a new list from string and joining it back for every letter check can be inefficient:
# Inefficient approach
char_list = list(idea)
char_list[0] = new_letter
new_word = ''.join(char_list)
Solution: Use string slicing which is more efficient:
# More efficient
new_word = chr(ord('a') + new_letter_idx) + idea[1:]
3. Incorrect Understanding of Valid Pairs
A critical pitfall is misunderstanding when a pair is valid. Both swapped words must NOT exist in the original set. A common error is checking only one direction:
# WRONG: Only checking if one swap doesn't exist if new_word not in ideas_set: total_pairs += 1 # This doesn't ensure the reverse swap is also valid
Solution: The algorithm correctly uses the pre-computed matrix to ensure both directions are valid. The count_matrix[j][i] value already represents words that start with 'j' and can validly swap to 'i'.
4. Off-by-One Errors with Character Indexing
Forgetting to handle the character-to-index conversion properly:
# WRONG: Using character directly as index
count_matrix[idea[0]][new_letter] # This will cause an error
# CORRECT: Convert to 0-based index
original_letter_idx = ord(idea[0]) - ord('a')
5. Not Using a Set for Lookups
Using the original list for membership checks instead of converting to a set:
# WRONG: O(n) lookup time
if new_word not in ideas: # If ideas is a list, this is slow
# CORRECT: O(1) lookup time
ideas_set = set(ideas)
if new_word not in ideas_set:
Optimized Solution with Fixes:
class Solution:
def distinctNames(self, ideas: List[str]) -> int:
ideas_set = set(ideas)
count_matrix = [[0] * 26 for _ in range(26)]
# Build frequency matrix
for idea in ideas:
first_letter_idx = ord(idea[0]) - ord('a')
suffix = idea[1:] # More efficient than list manipulation
for new_letter_idx in range(26):
new_word = chr(ord('a') + new_letter_idx) + suffix
if new_word not in ideas_set:
count_matrix[first_letter_idx][new_letter_idx] += 1
# Count valid pairs
total_pairs = 0
for idea in ideas:
first_letter_idx = ord(idea[0]) - ord('a')
suffix = idea[1:]
for new_letter_idx in range(26):
new_word = chr(ord('a') + new_letter_idx) + suffix
if new_word not in ideas_set:
total_pairs += count_matrix[new_letter_idx][first_letter_idx]
return total_pairs
Which of the following array represent a max heap?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!