Given two arrays of strings d[] and words[], where d[] contains a set of dictionary words and words[] contains search patterns. For each pattern in words[], determine whether it matches any word in d[].
A pattern matches a dictionary word if:
- Both strings have the same length.
- Every character matches at the same position.
- A (.) dot in the pattern can match any single lowercase alphabet.
Return the number of patterns in words[] that match at least one word in d[].
Examples:
Input: d[] = ["bad", "dad", "mad"], words[] = ["pad", "bad", ".ad", "b.."]
Output: 3
Explanation:
"pad" → No match
"bad" → Matches "bad"
".ad" → Matches "bad", "dad", and "mad"
"b.." → Matches "bad"
Therefore, 3 patterns have at least one matching word.Input: d[] = ["cat", "car", "dog", "door"], words[] = ["c.t", "ca.", "d..", "do.", "....."]
Output: 4
Explanation:
"c.t" → Matches "cat"
"ca." → Matches "cat" or "car"
"d.." → Matches "dog"
"do." → Matches "dog"
"....." → No match because there is no 5-letter dictionary word.
Therefore, 4 patterns have at least one matching word.
Table of Content
[Naive Approach] Check Every Pattern With Every Word
The simplest idea is to check every pattern from words[] against every word in d[].
Two strings match if they have the same length and, at every position, either their characters are equal or the character in the pattern is dot(.).
- Initialize count = 0.
- For every pattern in words[], compare it with every word in d[].
- If their lengths differ, skip that word.
- Compare both strings character by character. If pattern[j] == dot(.), it matches any character.
- Otherwise, pattern[j] must be equal to dictionary word[j].
- If all characters match, increment count and move to the next pattern.
- Return count.
#include <bits/stdc++.h>
using namespace std;
int countMatches(vector<string> &d, vector<string> &words)
{
// Stores the number of matched patterns.
int count = 0;
// Traverse every pattern in words[].
for (int i = 0; i < words.size(); i++)
{
string pattern = words[i];
// Check the current pattern against every
// word in the dictionary.
for (int j = 0; j < d.size(); j++)
{
string word = d[j];
// Patterns and words must have the same length.
if (pattern.size() != word.size())
continue;
// Assume that the pattern matches the word.
bool match = true;
// Compare characters at each position.
for (int k = 0; k < pattern.size(); k++)
{
// A dot can match any character.
if (pattern[k] == '.')
continue;
// Otherwise, characters must be equal.
if (pattern[k] != word[k])
{
match = false;
break;
}
}
// If the pattern matches this word,
// count it and move to the next pattern.
if (match)
{
count++;
break;
}
}
}
return count;
}
int main()
{
vector<string> d = {"bad", "dad", "mad"};
vector<string> words = {"pad", "bad", ".ad", "b.."};
cout << countMatches(d, words) << endl;
return 0;
}
class GFG {
static int countMatches(String[] d, String[] words)
{
// Stores the number of matched patterns.
int count = 0;
// Traverse every pattern in words[].
for (int i = 0; i < words.length; i++) {
String pattern = words[i];
// Check the current pattern against every
// word in the dictionary.
for (int j = 0; j < d.length; j++) {
String word = d[j];
// Patterns and words must have the same
// length.
if (pattern.length() != word.length())
continue;
// Assume that the pattern matches the word.
boolean match = true;
// Compare characters at each position.
for (int k = 0; k < pattern.length(); k++) {
// A dot can match any character.
if (pattern.charAt(k) == '.')
continue;
// Otherwise, characters must be equal.
if (pattern.charAt(k)
!= word.charAt(k)) {
match = false;
break;
}
}
// If the pattern matches this word,
// count it and move to the next pattern.
if (match) {
count++;
break;
}
}
}
return count;
}
public static void main(String[] args)
{
String[] d = { "bad", "dad", "mad" };
String[] words = { "pad", "bad", ".ad", "b.." };
System.out.println(countMatches(d, words));
}
}
def countMatches(d, words):
# Stores the number of matched patterns.
count = 0
# Traverse every pattern in words[].
for i in range(len(words)):
pattern = words[i]
# Check the current pattern against every
# word in the dictionary.
for j in range(len(d)):
word = d[j]
# Patterns and words must have the same length.
if len(pattern) != len(word):
continue
# Assume that the pattern matches the word.
match = True
# Compare characters at each position.
for k in range(len(pattern)):
# A dot can match any character.
if pattern[k] == '.':
continue
# Otherwise, characters must be equal.
if pattern[k] != word[k]:
match = False
break
# If the pattern matches this word,
# count it and move to the next pattern.
if match:
count += 1
break
return count
# Driver Code
if __name__ == "__main__":
d = ["bad", "dad", "mad"]
words = ["pad", "bad", ".ad", "b.."]
print(countMatches(d, words))
using System;
class GFG {
static int countMatches(string[] d, string[] words)
{
// Stores the number of matched patterns.
int count = 0;
// Traverse every pattern in words[].
for (int i = 0; i < words.Length; i++) {
string pattern = words[i];
// Check the current pattern against every
// word in the dictionary.
for (int j = 0; j < d.Length; j++) {
string word = d[j];
// Patterns and words must have the same
// length.
if (pattern.Length != word.Length)
continue;
// Assume that the pattern matches the word.
bool match = true;
// Compare characters at each position.
for (int k = 0; k < pattern.Length; k++) {
// A dot can match any character.
if (pattern[k] == '.')
continue;
// Otherwise, characters must be equal.
if (pattern[k] != word[k]) {
match = false;
break;
}
}
// If the pattern matches this word,
// count it and move to the next pattern.
if (match) {
count++;
break;
}
}
}
return count;
}
static void Main()
{
string[] d = { "bad", "dad", "mad" };
string[] words = { "pad", "bad", ".ad", "b.." };
Console.WriteLine(countMatches(d, words));
}
}
function countMatches(d, words)
{
// Stores the number of matched patterns.
let count = 0;
// Traverse every pattern in words[].
for (let i = 0; i < words.length; i++) {
let pattern = words[i];
// Check the current pattern against every
// word in the dictionary.
for (let j = 0; j < d.length; j++) {
let word = d[j];
// Patterns and words must have the same length.
if (pattern.length !== word.length)
continue;
// Assume that the pattern matches the word.
let match = true;
// Compare characters at each position.
for (let k = 0; k < pattern.length; k++) {
// A dot can match any character.
if (pattern[k] === ".")
continue;
// Otherwise, characters must be equal.
if (pattern[k] !== word[k]) {
match = false;
break;
}
}
// If the pattern matches this word,
// count it and move to the next pattern.
if (match) {
count++;
break;
}
}
}
return count;
}
// Driver Code
let d = [ "bad", "dad", "mad" ];
let words = [ "pad", "bad", ".ad", "b.." ];
console.log(countMatches(d, words));
Output
3
Time Complexity: O(m * n * L), where m is the no of words in d, n is the no of patterns in words and L is the maximum length string.
Auxiliary Space: O(1)
[Expected Approach] - Using Trie Data Structure
We store all words from d[] in a Trie.
While searching a pattern from words[], we do the following,
- Normal characters follow their corresponding Trie edge.
- A dot(.) can match any character, so we explore all possible child nodes.
- If we reach the end of the pattern at a complete word, the pattern matches.
- Create a Trie where each node has 26 children and an isEnd flag.
- Insert every word from d[] into the Trie.
- For every pattern in words[], start a DFS from the Trie root.
- At each position:
If the pattern character is a normal alphabet, follow its corresponding child.
If it is dot(.), recursively explore all existing child nodes. - If the entire pattern is consumed and the current node is an end-of-word node, return true.
- If the pattern matches at least one dictionary word, increment count.
#include <bits/stdc++.h>
using namespace std;
class TrieNode
{
public:
TrieNode *children[26];
// True if a complete word ends at this node.
bool isEnd;
TrieNode()
{
isEnd = false;
// Initially, no child nodes exist.
for (int i = 0; i < 26; i++)
children[i] = nullptr;
}
};
// Inserts a word into the Trie.
void insertWord(TrieNode *root, string &word)
{
// Start from the root.
TrieNode *curr = root;
// Traverse every character of the word.
for (char ch : word)
{
int index = ch - 'a';
// Create the child node if it does not exist.
if (curr->children[index] == nullptr)
curr->children[index] = new TrieNode();
// Move to the next node.
curr = curr->children[index];
}
// Mark the last node as the end of a word.
curr->isEnd = true;
}
// Searches a pattern in the Trie.
bool searchPattern(TrieNode *curr, string &pattern, int index)
{
// If the complete pattern has been processed,
// check whether a complete word ends here.
if (index == pattern.size())
return curr->isEnd;
// Get the current character of the pattern.
char ch = pattern[index];
// If the current character is a dot,
// it can match any alphabet.
if (ch == '.')
{
// Try every possible child node.
for (int i = 0; i < 26; i++)
{
// Continue only if the child exists.
if (curr->children[i] != nullptr)
{
// Recursively search the remaining pattern.
if (searchPattern(curr->children[i], pattern, index + 1))
return true;
}
}
// No child produced a valid match.
return false;
}
// For a normal character, follow its corresponding child.
int childIndex = ch - 'a';
// If the required child does not exist, no match is possible.
if (curr->children[childIndex] == nullptr)
return false;
// Continue searching from the corresponding child.
return searchPattern(curr->children[childIndex], pattern, index + 1);
}
int countMatches(vector<string> &d, vector<string> &words)
{
// Create the root of the Trie.
TrieNode *root = new TrieNode();
// Insert every dictionary word into the Trie.
for (string &word : d)
insertWord(root, word);
// Stores the number of matched patterns.
int count = 0;
// Traverse every pattern in words[].
for (string &pattern : words)
{
// Search the current pattern in the Trie.
if (searchPattern(root, pattern, 0))
count++;
}
return count;
}
int main()
{
vector<string> d = {"bad", "dad", "mad"};
vector<string> words = {"pad", "bad", ".ad", "b.."};
cout << countMatches(d, words) << endl;
return 0;
}
class TrieNode {
TrieNode[] children;
// True if a complete word ends at this node.
boolean isEnd;
TrieNode()
{
children = new TrieNode[26];
isEnd = false;
// Initially, no child nodes exist.
for (int i = 0; i < 26; i++)
children[i] = null;
}
}
class GFG {
static void insertWord(TrieNode root, String word)
{
// Start from the root.
TrieNode curr = root;
// Traverse every character of the word.
for (char ch : word.toCharArray()) {
int index = ch - 'a';
// Create the child node if it does not exist.
if (curr.children[index] == null)
curr.children[index] = new TrieNode();
// Move to the next node.
curr = curr.children[index];
}
// Mark the last node as the end of a word.
curr.isEnd = true;
}
// Searches a pattern in the Trie.
static boolean searchPattern(TrieNode curr,
String pattern, int index)
{
// If the complete pattern has been processed,
// check whether a complete word ends here.
if (index == pattern.length())
return curr.isEnd;
// Get the current character of the pattern.
char ch = pattern.charAt(index);
// If the current character is a dot,
// it can match any alphabet.
if (ch == '.') {
// Try every possible child node.
for (int i = 0; i < 26; i++) {
// Continue only if the child exists.
if (curr.children[i] != null) {
// Recursively search the remaining
// pattern.
if (searchPattern(curr.children[i],
pattern, index + 1))
return true;
}
}
// No child produced a valid match.
return false;
}
// For a normal character, follow its corresponding
// child.
int childIndex = ch - 'a';
// If the required child does not exist, no match is
// possible.
if (curr.children[childIndex] == null)
return false;
// Continue searching from the corresponding child.
return searchPattern(curr.children[childIndex],
pattern, index + 1);
}
static int countMatches(String[] d, String[] words)
{
// Create the root of the Trie.
TrieNode root = new TrieNode();
// Insert every dictionary word into the Trie.
for (String word : d)
insertWord(root, word);
// Stores the number of matched patterns.
int count = 0;
// Traverse every pattern in words[].
for (String pattern : words) {
// Search the current pattern in the Trie.
if (searchPattern(root, pattern, 0))
count++;
}
return count;
}
public static void main(String[] args)
{
String[] d = { "bad", "dad", "mad" };
String[] words = { "pad", "bad", ".ad", "b.." };
System.out.println(countMatches(d, words));
}
}
class TrieNode:
def __init__(self):
self.children = [None] * 26
# True if a complete word ends at this node.
self.isEnd = False
# Inserts a word into the Trie.
def insertWord(root, word):
# Start from the root.
curr = root
# Traverse every character of the word.
for ch in word:
index = ord(ch) - ord('a')
# Create the child node if it does not exist.
if curr.children[index] is None:
curr.children[index] = TrieNode()
# Move to the next node.
curr = curr.children[index]
# Mark the last node as the end of a word.
curr.isEnd = True
# Searches a pattern in the Trie.
def searchPattern(curr, pattern, index):
# If the complete pattern has been processed,
# check whether a complete word ends here.
if index == len(pattern):
return curr.isEnd
# Get the current character of the pattern.
ch = pattern[index]
# If the current character is a dot,
# it can match any alphabet.
if ch == '.':
# Try every possible child node.
for i in range(26):
# Continue only if the child exists.
if curr.children[i] is not None:
# Recursively search the remaining pattern.
if searchPattern(curr.children[i], pattern, index + 1):
return True
# No child produced a valid match.
return False
# For a normal character, follow its corresponding child.
childIndex = ord(ch) - ord('a')
# If the required child does not exist, no match is possible.
if curr.children[childIndex] is None:
return False
# Continue searching from the corresponding child.
return searchPattern(curr.children[childIndex], pattern, index + 1)
def countMatches(d, words):
# Create the root of the Trie.
root = TrieNode()
# Insert every dictionary word into the Trie.
for word in d:
insertWord(root, word)
# Stores the number of matched patterns.
count = 0
# Traverse every pattern in words[].
for pattern in words:
# Search the current pattern in the Trie.
if searchPattern(root, pattern, 0):
count += 1
return count
# Driver Code
if __name__ == "__main__":
d = ["bad", "dad", "mad"]
words = ["pad", "bad", ".ad", "b.."]
print(countMatches(d, words))
using System;
class TrieNode {
public TrieNode[] children;
// True if a complete word ends at this node.
public bool isEnd;
public TrieNode()
{
children = new TrieNode[26];
isEnd = false;
// Initially, no child nodes exist.
for (int i = 0; i < 26; i++)
children[i] = null;
}
}
class GFG {
// Inserts a word into the Trie.
static void InsertWord(TrieNode root, string word)
{
// Start from the root.
TrieNode curr = root;
// Traverse every character of the word.
foreach(char ch in word)
{
int index = ch - 'a';
// Create the child node if it does not exist.
if (curr.children[index] == null)
curr.children[index] = new TrieNode();
// Move to the next node.
curr = curr.children[index];
}
// Mark the last node as the end of a word.
curr.isEnd = true;
}
// Searches a pattern in the Trie.
static bool SearchPattern(TrieNode curr, string pattern,
int index)
{
// If the complete pattern has been processed,
// check whether a complete word ends here.
if (index == pattern.Length)
return curr.isEnd;
// Get the current character of the pattern.
char ch = pattern[index];
// If the current character is a dot,
// it can match any alphabet.
if (ch == '.') {
// Try every possible child node.
for (int i = 0; i < 26; i++) {
// Continue only if the child exists.
if (curr.children[i] != null) {
// Recursively search the remaining
// pattern.
if (SearchPattern(curr.children[i],
pattern, index + 1))
return true;
}
}
// No child produced a valid match.
return false;
}
// For a normal character, follow its corresponding
// child.
int childIndex = ch - 'a';
// If the required child does not exist, no match is
// possible.
if (curr.children[childIndex] == null)
return false;
// Continue searching from the corresponding child.
return SearchPattern(curr.children[childIndex],
pattern, index + 1);
}
static int countMatches(string[] d, string[] words)
{
// Create the root of the Trie.
TrieNode root = new TrieNode();
// Insert every dictionary word into the Trie.
foreach(string word in d) InsertWord(root, word);
// Stores the number of matched patterns.
int count = 0;
// Traverse every pattern in words[].
foreach(string pattern in words)
{
// Search the current pattern in the Trie.
if (SearchPattern(root, pattern, 0))
count++;
}
return count;
}
static void Main()
{
string[] d = { "bad", "dad", "mad" };
string[] words = { "pad", "bad", ".ad", "b.." };
Console.WriteLine(countMatches(d, words));
}
}
class TrieNode {
constructor()
{
this.children = new Array(26).fill(null);
// True if a complete word ends at this node.
this.isEnd = false;
}
}
// Inserts a word into the Trie.
function insertWord(root, word)
{
// Start from the root.
let curr = root;
// Traverse every character of the word.
for (let ch of word) {
let index = ch.charCodeAt(0) - "a".charCodeAt(0);
// Create the child node if it does not exist.
if (curr.children[index] === null)
curr.children[index] = new TrieNode();
// Move to the next node.
curr = curr.children[index];
}
// Mark the last node as the end of a word.
curr.isEnd = true;
}
// Searches a pattern in the Trie.
function searchPattern(curr, pattern, index)
{
// If the complete pattern has been processed,
// check whether a complete word ends here.
if (index === pattern.length)
return curr.isEnd;
// Get the current character of the pattern.
let ch = pattern[index];
// If the current character is a dot,
// it can match any alphabet.
if (ch === ".") {
// Try every possible child node.
for (let i = 0; i < 26; i++) {
// Continue only if the child exists.
if (curr.children[i] !== null) {
// Recursively search the remaining pattern.
if (searchPattern(curr.children[i], pattern,
index + 1))
return true;
}
}
// No child produced a valid match.
return false;
}
// For a normal character, follow its corresponding
// child.
let childIndex = ch.charCodeAt(0) - "a".charCodeAt(0);
// If the required child does not exist, no match is
// possible.
if (curr.children[childIndex] === null)
return false;
// Continue searching from the corresponding child.
return searchPattern(curr.children[childIndex], pattern,
index + 1);
}
function countMatches(d, words)
{
// Create the root of the Trie.
let root = new TrieNode();
// Insert every dictionary word into the Trie.
for (let word of d)
insertWord(root, word);
// Stores the number of matched patterns.
let count = 0;
// Traverse every pattern in words[].
for (let pattern of words) {
// Search the current pattern in the Trie.
if (searchPattern(root, pattern, 0))
count++;
}
return count;
}
// Driver Code
let d = [ "bad", "dad", "mad" ];
let words = [ "pad", "bad", ".ad", "b.." ];
console.log(countMatches(d, words));
Output
3
Time Complexity: O(m * L + n * 26^L), where m is the no of words in d, n is the no of patterns in words and L is the maximum length string.
Auxiliary Space: O(m * L)