Published: Jan 21, 2023
Problem Description
You are given two 0-indexed arrays of strings
startWords
andtargetWords
. Each string consists of lowercase English letters only. For each string intargetWords
, check if it is possible to choose a string fromstartWords
and perform a conversion operation on it to be equal to that fromtargetWords
. The conversion operation is described in the following two steps:
- Append any lowercase letter that is not present in the string to its end.
- For example, if the string is “abc”, the letters ‘d’, ‘e’, or ‘y’ can be added to it, but not ‘a’. If ‘d’ is added, the resulting string will be “abcd”.
- Rearrange the letters of the new string in any arbitrary order.
- For example, “abcd” can be rearranged to “acbd”, “bacd”, “cbda”, and so on. Note that it can also be rearranged to “abcd” itself.
Return the number of strings in
targetWords
that can be obtained by performing the operations on any string ofstartWords
.Note that you will only be verifying if the string in
targetWords
can be obtained from a string in startWords by performing the operations. The strings instartWords
do not actually change during this process.Constraints:
1 <= startWords.length, targetWords.length <= 5 * 10**4
1 <= startWords[i].length, targetWords[j].length <= 26
- Each string of
startWords
andtargetWords
consists of lowercase English letters only.- No letter occurs more than once in any string of
startWords
ortargetWords
.https://leetcode.com/problems/count-words-obtained-after-adding-a-letter/
Examples
Exmaple 1
Input: startWords = ["ant","act","tack"], targetWords = ["tack","act","acti"]
Output: 2
Explanation:
act -> tack and act -> acti
Example 2
Input: startWords = ["ab","a"], targetWords = ["abc","abcd"]
Output: 1
Explanation:
ab -> abc
How to Solve
This problem is an anagram comparison. The order of characters doesn’t matter. Sorting words and creating one letter shorter word from target words would be a reasonable approach. However, it runs slow since it needs a lot of sorting and substring creation.
Instead, the approach here uses the idea of bitmask. Each word is converted to an integer value by shifting and adding up. The comparison is done by the integer subtraction. If the integer for one letter shorter string is in the start word set, count up.
The bitmask approach runs faster than sorting with substring match in general. However, as for Python, the sorting/substring match approach runs reasonably fast. So, Python has two solutions.
Solution
#include <unordered_set>
#include <vector>
using namespace std;
class CountWordsObtainedAfterAddingALetter {
private:
int s2i(string &s) {
int ret = 0;
for (auto c : s) {
ret += 1 << c - 'a';
}
return ret;
}
public:
int wordCount(vector<string>& startWords, vector<string>& targetWords) {
unordered_set<int> starts;
for (auto &word : startWords) {
starts.insert(s2i(word));
}
int result = 0, wi;
for (auto &word : targetWords) {
wi = s2i(word);
for (auto &c : word) {
if (starts.find(wi - (1 << c - 'a')) != starts.end()) {
result++;
break;
}
}
}
return result;
}
};
import java.util.*;
public class CountWordsObtainedAfterAddingALetter {
private int s2i(String s) {
int ret = 0;
for (char c : s.toCharArray()) {
ret += 1 << c - 'a';
}
return ret;
}
public int wordCount(String[] startWords, String[] targetWords) {
Set<Integer> starts = new HashSet<>();
for (String w : startWords) {
starts.add(s2i(w));
}
int result = 0;
for (String w : targetWords) {
int wi = s2i(w);
for (int i = 0; i < w.length(); ++i) {
if (starts.contains(wi - (1 << w.charAt(i) - 'a'))) {
result++;
break;
}
}
}
return result;
}
}
/**
* @param {string[]} startWords
* @param {string[]} targetWords
* @return {number}
*/
var wordCount = function(startWords, targetWords) {
const base = "a".charCodeAt(0), starts = new Set();
const s2i = (s) => {
let ret = 0;
for (let c of s) {
ret += 1 << (c.charCodeAt(0) - base);
}
return ret;
}
for (let word of startWords) {
starts.add(s2i(word));
}
let result = 0;
for (let word of targetWords) {
let wi = s2i(word);
for (let c of word) {
if (starts.has(wi - (1 << (c.charCodeAt(0) - base)))) {
result += 1;
break;
}
}
}
return result;
};
class CountWordsObtainedAfterAddingALetter:
def wordCount(self, startWords: List[str], targetWords: List[str]) -> int:
base, starts, result = ord('a'), set(), 0
def s2i(s):
ret = 0
for c in s:
ret += 1 << ord(c) - base
return ret
for word in startWords:
starts.add(s2i(word))
for word in targetWords:
wi = s2i(word)
for c in word:
if (wi - (1 << ord(c) - base)) in starts:
result += 1
break
return result
def wordCount2(self, startWords: List[str], targetWords: List[str]) -> int:
starts = set()
for word in startWords:
word = "".join(sorted(word))
starts.add(word)
result = 0
for word in targetWords:
word = "".join(sorted(word))
for i in range(len(word)):
if word[:i] + word[i + 1:] in starts:
result += 1
break
return result
require 'set'
# @param {String[]} start_words
# @param {String[]} target_words
# @return {Integer}
def word_count(start_words, target_words)
base, starts, result = 'a'.ord, Set.new, 0
s2i = lambda do |s|
ret = 0
(0...s.size).each do |i|
ret += (1 << s[i].ord - base)
end
ret
end
start_words.each do |word|
starts.add(s2i.call(word))
end
target_words.each do |word|
wi = s2i.call(word)
(0...word.size).each do |i|
if starts.include?(wi - (1 << (word[i].ord - base)))
result += 1;
break
end
end
end
result
end
Complexities
- Time:
O(m + n)
– m, n: number of start and target words. Sorting/looping word is constant since each word has at most 26 characters. - Space:
O(m)