Facebook Pixel

1023. Camelcase Matching

MediumTrieArrayTwo PointersStringString Matching
Leetcode Link

Problem Description

You are given an array of strings queries and a string pattern. Your task is to determine which query strings match the pattern according to specific rules.

A query string matches the pattern if you can transform the pattern into the query by inserting lowercase English letters at any positions (including at the beginning, end, or between characters). You can choose to insert no characters at all if the pattern already matches the query.

The key rules are:

  • You can only insert lowercase letters into the pattern
  • You cannot remove or rearrange characters from the pattern
  • All characters from the pattern must appear in the same order in the query
  • Any uppercase letters in the query must match exactly with the pattern

For example:

  • If pattern = "FB" and query = "FooBar", this matches because we can insert lowercase letters "oo" and "ar" to transform "FB" into "FooBar"
  • If pattern = "FB" and query = "FooBarTest", this doesn't match because "Test" contains an uppercase 'T' that's not in the pattern
  • If pattern = "FB" and query = "Foo", this doesn't match because 'B' from the pattern is missing

Return a boolean array answer where answer[i] is true if queries[i] matches the pattern, and false otherwise.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem is essentially asking us to check if we can match a pattern string with a query string by only adding lowercase letters to the pattern. This immediately suggests a two-pointer approach where we try to match characters from both strings.

Let's think about what makes a valid match:

  1. Every character in the pattern must appear in the query in the same order
  2. Between pattern characters, we can only have lowercase letters in the query
  3. Any uppercase letter in the query that's not in the pattern makes it invalid

This leads us to a greedy matching strategy: for each character in the pattern, we try to find it in the query while skipping only lowercase letters. If we encounter an uppercase letter that doesn't match the current pattern character, we know it's impossible to match.

The key insight is that uppercase letters act as "anchors" - they must match exactly with the pattern, while lowercase letters are flexible and can be inserted anywhere. So our algorithm becomes:

  • Try to match each pattern character with the query
  • While searching for a match, skip lowercase letters (these are the "inserted" characters)
  • If we hit an uppercase letter that doesn't match, fail immediately
  • After matching all pattern characters, ensure any remaining characters in the query are lowercase

This greedy approach works because we're only allowed to insert characters, not remove or rearrange them. So the first occurrence of each pattern character in the query (following the rules) is the only valid match position.

Learn more about Trie and Two Pointers patterns.

Solution Approach

The solution uses a two-pointer technique to check if each query matches the pattern. We implement a helper function check(s, t) where s is the query string and t is the pattern.

Here's how the algorithm works:

  1. Initialize two pointers: Set i = 0 for traversing the query string s and j = 0 for traversing the pattern t. Also get the lengths m = len(s) and n = len(t).

  2. Match pattern characters: While j < n (still have pattern characters to match):

    • Skip lowercase letters in the query: Use a while loop to advance i as long as s[i] is a lowercase letter and doesn't match t[j]
    • Check for match: If we've reached the end of s (i == m) or the characters don't match (s[i] != t[j]), return False
    • If characters match, advance both pointers: i += 1 and j += 1
  3. Handle remaining query characters: After matching all pattern characters, we need to ensure any remaining characters in the query are lowercase:

    • Use another while loop to skip lowercase letters: while i < m and s[i].islower(): i += 1
    • Return True if we've consumed the entire query (i == m), otherwise False
  4. Process all queries: Apply the check function to each query in the list using a list comprehension: [check(q, pattern) for q in queries]

The time complexity is O(n ร— m) where n is the total number of characters across all queries and m is the length of the pattern. For each query, we potentially scan through all its characters once.

The space complexity is O(1) for the checking function itself (only using pointers), though the output array takes O(k) space where k is the number of queries.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with pattern = "FB" and query = "FooBar".

Step 1: Initialize pointers

  • i = 0 (pointing to 'F' in "FooBar")
  • j = 0 (pointing to 'F' in "FB")
  • m = 6 (length of "FooBar")
  • n = 2 (length of "FB")

Step 2: Match first pattern character 'F'

  • While j < 2 (true, j=0):
    • Check if we need to skip lowercase letters: s[0] is 'F' (uppercase), so no skipping needed
    • Check match: s[0] = 'F' equals t[0] = 'F' โœ“
    • Advance both pointers: i = 1, j = 1

Step 3: Match second pattern character 'B'

  • While j < 2 (true, j=1):
    • Skip lowercase letters:
      • s[1] = 'o' is lowercase and doesn't equal 'B', so i = 2
      • s[2] = 'o' is lowercase and doesn't equal 'B', so i = 3
      • s[3] = 'B' is uppercase, stop skipping
    • Check match: s[3] = 'B' equals t[1] = 'B' โœ“
    • Advance both pointers: i = 4, j = 2

Step 4: Check remaining characters

  • Now j = 2, so we've matched all pattern characters
  • Check remaining query characters (indices 4-5):
    • s[4] = 'a' is lowercase, skip: i = 5
    • s[5] = 'r' is lowercase, skip: i = 6
  • We've reached the end (i = 6 = m)
  • Return True โœ“

The query "FooBar" matches pattern "FB" because we successfully matched all pattern characters in order ('F' and 'B') while only encountering lowercase letters ('o', 'o', 'a', 'r') in between, which represent our valid insertions.

Solution Implementation

1class Solution:
2    def camelMatch(self, queries: List[str], pattern: str) -> List[bool]:
3        def is_match(query: str, pattern: str) -> bool:
4            """
5            Check if a query string matches the given pattern.
6          
7            Rules:
8            - All characters in pattern must appear in query in the same order
9            - Lowercase letters can be inserted anywhere in query
10            - Uppercase letters in query must match exactly with pattern
11            """
12            query_len, pattern_len = len(query), len(pattern)
13            query_idx = 0
14            pattern_idx = 0
15          
16            # Try to match all characters in the pattern
17            while pattern_idx < pattern_len:
18                # Skip lowercase letters in query that don't match current pattern character
19                while query_idx < query_len and query[query_idx] != pattern[pattern_idx] and query[query_idx].islower():
20                    query_idx += 1
21              
22                # Check if we've exhausted the query or current characters don't match
23                if query_idx == query_len or query[query_idx] != pattern[pattern_idx]:
24                    return False
25              
26                # Move both pointers forward after successful match
27                query_idx += 1
28                pattern_idx += 1
29          
30            # After matching all pattern characters, only lowercase letters should remain in query
31            while query_idx < query_len and query[query_idx].islower():
32                query_idx += 1
33          
34            # All characters in query should be consumed
35            return query_idx == query_len
36      
37        # Apply the matching function to all queries
38        return [is_match(query, pattern) for query in queries]
39
1class Solution {
2    /**
3     * Checks if each query string matches the camelCase pattern.
4     * A query matches if we can insert lowercase letters into the pattern to get the query.
5     * 
6     * @param queries Array of query strings to check
7     * @param pattern The camelCase pattern to match against
8     * @return List of boolean values indicating if each query matches the pattern
9     */
10    public List<Boolean> camelMatch(String[] queries, String pattern) {
11        List<Boolean> results = new ArrayList<>();
12      
13        // Check each query against the pattern
14        for (String query : queries) {
15            results.add(isPatternMatch(query, pattern));
16        }
17      
18        return results;
19    }
20  
21    /**
22     * Checks if a string matches the given pattern following camelCase rules.
23     * The string matches if:
24     * 1. All characters in pattern appear in the same order in string
25     * 2. Only lowercase letters can be inserted between pattern characters
26     * 3. No uppercase letters exist that are not in the pattern
27     * 
28     * @param string The string to check
29     * @param pattern The pattern to match against
30     * @return true if string matches pattern, false otherwise
31     */
32    private boolean isPatternMatch(String string, String pattern) {
33        int stringLength = string.length();
34        int patternLength = pattern.length();
35        int stringIndex = 0;
36        int patternIndex = 0;
37      
38        // Try to match each character in the pattern
39        for (; patternIndex < patternLength; stringIndex++, patternIndex++) {
40            // Skip lowercase letters in string until we find the matching pattern character
41            while (stringIndex < stringLength && 
42                   string.charAt(stringIndex) != pattern.charAt(patternIndex) && 
43                   Character.isLowerCase(string.charAt(stringIndex))) {
44                stringIndex++;
45            }
46          
47            // Check if we've exhausted the string or current characters don't match
48            if (stringIndex == stringLength || 
49                string.charAt(stringIndex) != pattern.charAt(patternIndex)) {
50                return false;
51            }
52        }
53      
54        // After matching all pattern characters, ensure only lowercase letters remain
55        while (stringIndex < stringLength && 
56               Character.isLowerCase(string.charAt(stringIndex))) {
57            stringIndex++;
58        }
59      
60        // String matches if we've processed all characters
61        return stringIndex == stringLength;
62    }
63}
64
1class Solution {
2public:
3    vector<bool> camelMatch(vector<string>& queries, string pattern) {
4        vector<bool> result;
5      
6        // Lambda function to check if a query matches the pattern
7        auto isMatch = [](string& query, string& pattern) {
8            int queryLength = query.size();
9            int patternLength = pattern.size();
10            int queryIndex = 0;
11            int patternIndex = 0;
12          
13            // Try to match each character in the pattern
14            for (; patternIndex < patternLength; ++queryIndex, ++patternIndex) {
15                // Skip lowercase letters in query that don't match current pattern character
16                while (queryIndex < queryLength && 
17                       query[queryIndex] != pattern[patternIndex] && 
18                       islower(query[queryIndex])) {
19                    ++queryIndex;
20                }
21              
22                // Check if we've exhausted the query or current characters don't match
23                if (queryIndex == queryLength || query[queryIndex] != pattern[patternIndex]) {
24                    return false;
25                }
26            }
27          
28            // After matching all pattern characters, ensure remaining query characters are lowercase
29            while (queryIndex < queryLength && islower(query[queryIndex])) {
30                ++queryIndex;
31            }
32          
33            // Return true if we've consumed the entire query string
34            return queryIndex == queryLength;
35        };
36      
37        // Check each query against the pattern
38        for (auto& query : queries) {
39            result.push_back(isMatch(query, pattern));
40        }
41      
42        return result;
43    }
44};
45
1/**
2 * Checks if each query string matches the given pattern according to camelCase matching rules.
3 * A query matches if we can insert lowercase letters to make it equal to the pattern.
4 * @param queries - Array of query strings to check against the pattern
5 * @param pattern - The pattern string to match against
6 * @returns Array of boolean values indicating whether each query matches the pattern
7 */
8function camelMatch(queries: string[], pattern: string): boolean[] {
9    /**
10     * Helper function to check if a query string matches the pattern.
11     * Rules:
12     * - All characters in pattern must appear in query in the same order
13     * - Between pattern characters, only lowercase letters can be inserted in query
14     * - After matching all pattern characters, only lowercase letters can remain in query
15     * @param query - The query string to check
16     * @param pattern - The pattern string to match against
17     * @returns true if query matches pattern, false otherwise
18     */
19    const isMatch = (query: string, pattern: string): boolean => {
20        const queryLength: number = query.length;
21        const patternLength: number = pattern.length;
22        let queryIndex: number = 0;
23        let patternIndex: number = 0;
24      
25        // Try to match each character in the pattern
26        for (; patternIndex < patternLength; queryIndex++, patternIndex++) {
27            // Skip lowercase letters in query until we find the next pattern character
28            // 97 is the ASCII code for 'a', so >= 97 means lowercase letter
29            while (queryIndex < queryLength && 
30                   query[queryIndex] !== pattern[patternIndex] && 
31                   query[queryIndex].codePointAt(0)! >= 97) {
32                queryIndex++;
33            }
34          
35            // If we've reached the end of query or characters don't match, pattern doesn't match
36            if (queryIndex === queryLength || query[queryIndex] !== pattern[patternIndex]) {
37                return false;
38            }
39        }
40      
41        // After matching all pattern characters, check remaining characters in query
42        // Only lowercase letters are allowed
43        while (queryIndex < queryLength && query[queryIndex].codePointAt(0)! >= 97) {
44            queryIndex++;
45        }
46      
47        // Pattern matches if we've consumed the entire query string
48        return queryIndex === queryLength;
49    };
50  
51    // Process each query and store the results
52    const results: boolean[] = [];
53    for (const query of queries) {
54        results.push(isMatch(query, pattern));
55    }
56  
57    return results;
58}
59

Time and Space Complexity

Time Complexity: O(k * (m + n)), where k is the number of queries, m is the average length of each query string, and n is the length of the pattern string.

  • The outer list comprehension iterates through all k queries
  • For each query, the check function is called, which uses two pointers to traverse through the query string and pattern
  • In the worst case, the first while loop iterates through all characters in both the query string (m) and pattern (n)
  • The second while loop after pattern matching may iterate through remaining lowercase characters in the query string, but this is still bounded by m
  • Therefore, for each query, the time complexity is O(m + n)
  • Total time complexity: O(k * (m + n))

Space Complexity: O(k), where k is the number of queries.

  • The check function uses only a constant amount of extra space with variables m, n, i, and j
  • The main space consumption comes from the output list that stores k boolean results
  • The list comprehension creates a new list of size k to store the results
  • No recursive calls or additional data structures are used that scale with input size
  • Therefore, the space complexity is O(k)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Handling of Uppercase Letters in the Query

The Pitfall: A common mistake is not properly validating that uppercase letters appearing in the query but not in the pattern should cause a mismatch. The algorithm might incorrectly skip over uppercase letters thinking they're optional like lowercase letters.

Example of the Bug:

# Incorrect implementation that treats uppercase letters like lowercase
while query_idx < query_len and query[query_idx] != pattern[pattern_idx]:
    query_idx += 1  # Wrong! This skips ANY character, including uppercase

The Problem: With pattern = "FB" and query = "FooTBar", this buggy code would incorrectly return True because it skips the uppercase 'T' that isn't in the pattern.

The Solution: Always check if the character is lowercase before skipping:

# Correct implementation
while query_idx < query_len and query[query_idx] != pattern[pattern_idx] and query[query_idx].islower():
    query_idx += 1  # Only skip lowercase letters

2. Forgetting to Validate Remaining Characters After Pattern Match

The Pitfall: After successfully matching all pattern characters, forgetting to check that any remaining characters in the query are only lowercase letters.

Example of the Bug:

def is_match(query, pattern):
    # ... matching logic ...
  
    # Bug: Immediately return True after matching pattern
    return pattern_idx == pattern_len  # Wrong! Doesn't check remaining query characters

The Problem: With pattern = "FB" and query = "FooBarTest", this would incorrectly return True even though "Test" contains an uppercase 'T' not in the pattern.

The Solution: Always validate the remaining query characters:

# After matching all pattern characters
while query_idx < query_len and query[query_idx].islower():
    query_idx += 1

return query_idx == query_len  # Ensures all remaining characters were lowercase

3. Edge Case: Empty Pattern

The Pitfall: Not handling the case where the pattern is empty, which should only match queries containing all lowercase letters.

The Solution: The current implementation handles this correctly because when pattern_len = 0, the first while loop is skipped entirely, and the second while loop validates that all query characters are lowercase. However, it's worth being aware of this edge case during testing.

Test Cases to Verify:

  • pattern = "", query = "abc" โ†’ True
  • pattern = "", query = "Abc" โ†’ False
  • pattern = "", query = "" โ†’ True
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More