Facebook Pixel

3606. Coupon Code Validator

EasyArrayHash TableStringSorting
Leetcode Link

Problem Description

You are given three arrays of equal length n that describe properties of n coupons:

  • code[i]: a string representing the coupon's identifier
  • businessLine[i]: a string denoting the coupon's business category
  • isActive[i]: a boolean indicating if the coupon is currently active

A coupon is considered valid when it meets all three conditions:

  1. Its code is non-empty and contains only alphanumeric characters (a-z, A-Z, 0-9) and underscores (_)
  2. Its businessLine is exactly one of these four categories: "electronics", "grocery", "pharmacy", or "restaurant"
  3. Its isActive status is true

Your task is to return an array containing the codes of all valid coupons. The output should be sorted using a two-level sorting criteria:

  • Primary sort: By business line in this specific order: "electronics", "grocery", "pharmacy", "restaurant"
  • Secondary sort: Within each business category, sort codes lexicographically (alphabetically in ascending order)

For example, if you have valid coupons with codes ["ABC", "XYZ"] in electronics and ["DEF"] in grocery, the output would be ["ABC", "XYZ", "DEF"] - first all electronics coupons (sorted alphabetically), then all grocery coupons (sorted alphabetically), and so on.

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

Intuition

The problem is essentially asking us to filter and sort data based on specific criteria. Let's think about how to approach this step by step.

First, we need to identify which coupons are valid. Since we have three conditions that must all be true, we need to check each coupon against all three criteria. The natural approach is to iterate through all coupons and keep track of which ones pass all the validation checks.

For the code validation, we need to ensure the string is non-empty and contains only allowed characters (alphanumeric and underscore). We can check each character individually to see if it meets these requirements.

For the business line validation, since we have exactly four valid categories, we can use a set to store these valid options and check membership efficiently. Using a set like {"electronics", "grocery", "pharmacy", "restaurant"} makes the validation a simple in operation.

The active status check is straightforward - just verify that isActive[i] is true.

Once we identify all valid coupons, we face a sorting challenge. We need to sort by two criteria: first by business line (in a specific order), then by code (lexicographically). The key insight here is that we can't use simple alphabetical sorting for business lines because the required order ("electronics", "grocery", "pharmacy", "restaurant") doesn't match alphabetical order.

To handle this custom sorting, we can store the indices of valid coupons first, then sort these indices using a custom key function. The key function would return a tuple (businessLine[i], code[i]) for each index i. Python's sorting naturally handles tuples by comparing the first element first, then the second element if the first elements are equal. Since the business lines are strings and happen to be in the correct order alphabetically, this approach works perfectly.

Finally, we use the sorted indices to extract the corresponding codes from the original code array and return them in the correct order.

Learn more about Sorting patterns.

Solution Approach

The implementation follows a simulation approach where we directly check each condition and collect valid coupons. Here's how the solution works:

1. Helper Function for Code Validation

We define a helper function check(s) to validate coupon codes:

def check(s: str) -> bool:
    if not s:
        return False
    for c in s:
        if not (c.isalpha() or c.isdigit() or c == "_"):
            return False
    return True

This function returns False if the string is empty or contains any character that isn't alphanumeric or underscore.

2. Define Valid Business Categories

We create a set containing the four valid business categories:

bs = {"electronics", "grocery", "pharmacy", "restaurant"}

Using a set provides O(1) lookup time for membership checking.

3. Collect Valid Coupon Indices

We iterate through all coupons using enumerate and zip to process the three arrays simultaneously:

idx = []
for i, (c, b, a) in enumerate(zip(code, businessLine, isActive)):
    if a and b in bs and check(c):
        idx.append(i)

For each coupon at index i, we check:

  • a (isActive) is True
  • b (businessLine) is in our valid categories set
  • c (code) passes our validation function

If all conditions are met, we store the index i in our idx list.

4. Custom Sorting

We sort the collected indices using a custom key function:

idx.sort(key=lambda i: (businessLine[i], code[i]))

The key function returns a tuple (businessLine[i], code[i]) for each index. Python sorts tuples lexicographically - first by the first element, then by the second if the first elements are equal.

Since the business categories happen to be in alphabetical order matching our required order ("electronics" < "grocery" < "pharmacy" < "restaurant"), this sorting naturally gives us the correct ordering.

5. Extract and Return Results

Finally, we use list comprehension to extract the codes of valid coupons in sorted order:

return [code[i] for i in idx]

Time Complexity: O(nΒ·m + kΒ·log(k)) where n is the number of coupons, m is the average length of coupon codes, and k is the number of valid coupons.

Space Complexity: O(k) for storing the indices of valid coupons.

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 a concrete example to illustrate how the solution works:

Input:

  • code = ["SAVE20", "10OFF", "FREE@SHIP", "DEAL_5", ""]
  • businessLine = ["electronics", "grocery", "electronics", "pharmacy", "grocery"]
  • isActive = [True, True, True, True, False]

Step 1: Validate Each Coupon

Let's check each coupon (index 0 to 4):

Index 0: code="SAVE20", businessLine="electronics", isActive=True

  • Code validation: "SAVE20" contains only letters and digits βœ“
  • Business line: "electronics" is in valid set βœ“
  • Active status: True βœ“
  • Valid - add index 0 to our list

Index 1: code="10OFF", businessLine="grocery", isActive=True

  • Code validation: "10OFF" contains only digits and letters βœ“
  • Business line: "grocery" is in valid set βœ“
  • Active status: True βœ“
  • Valid - add index 1 to our list

Index 2: code="FREE@SHIP", businessLine="electronics", isActive=True

  • Code validation: "FREE@SHIP" contains '@' which is not allowed βœ—
  • Invalid - skip this coupon

Index 3: code="DEAL_5", businessLine="pharmacy", isActive=True

  • Code validation: "DEAL_5" contains letters, underscore, and digit βœ“
  • Business line: "pharmacy" is in valid set βœ“
  • Active status: True βœ“
  • Valid - add index 3 to our list

Index 4: code="", businessLine="grocery", isActive=False

  • Code validation: empty string βœ—
  • Active status: False βœ—
  • Invalid - skip this coupon

After validation, idx = [0, 1, 3] (indices of valid coupons)

Step 2: Sort the Valid Indices

We sort using the key function lambda i: (businessLine[i], code[i]):

  • Index 0: key = ("electronics", "SAVE20")
  • Index 1: key = ("grocery", "10OFF")
  • Index 3: key = ("pharmacy", "DEAL_5")

Sorting these tuples:

  1. First by business line: "electronics" < "grocery" < "pharmacy"
  2. Within same business line: sort by code alphabetically

Sorted order: idx = [0, 1, 3] (no change needed as they're already in the correct order)

Step 3: Extract Codes

Using the sorted indices, extract the codes:

  • code[0] = "SAVE20"
  • code[1] = "10OFF"
  • code[3] = "DEAL_5"

Output: ["SAVE20", "10OFF", "DEAL_5"]

The result contains all valid coupon codes, sorted first by business line priority (electronics β†’ grocery β†’ pharmacy), then alphabetically within each category.

Solution Implementation

1class Solution:
2    def validateCoupons(
3        self, code: List[str], businessLine: List[str], isActive: List[bool]
4    ) -> List[str]:
5        """
6        Validates and returns sorted coupon codes based on specific criteria.
7      
8        Args:
9            code: List of coupon codes to validate
10            businessLine: List of business lines corresponding to each coupon
11            isActive: List of boolean flags indicating if each coupon is active
12          
13        Returns:
14            List of valid coupon codes sorted by business line and then by code
15        """
16      
17        def is_valid_code(coupon_code: str) -> bool:
18            """
19            Checks if a coupon code contains only alphanumeric characters and underscores.
20          
21            Args:
22                coupon_code: The coupon code string to validate
23              
24            Returns:
25                True if the code is valid, False otherwise
26            """
27            # Empty strings are invalid
28            if not coupon_code:
29                return False
30          
31            # Check each character is alphanumeric or underscore
32            for char in coupon_code:
33                if not (char.isalpha() or char.isdigit() or char == "_"):
34                    return False
35          
36            return True
37      
38        # Define valid business lines
39        valid_business_lines = {"electronics", "grocery", "pharmacy", "restaurant"}
40      
41        # Collect indices of valid coupons
42        valid_indices = []
43      
44        # Iterate through all coupons with their index
45        for index, (coupon_code, business, is_active_flag) in enumerate(
46            zip(code, businessLine, isActive)
47        ):
48            # Check all validation criteria:
49            # 1. Coupon must be active
50            # 2. Business line must be in the valid set
51            # 3. Coupon code must pass character validation
52            if (is_active_flag and 
53                business in valid_business_lines and 
54                is_valid_code(coupon_code)):
55                valid_indices.append(index)
56      
57        # Sort indices by business line first, then by coupon code
58        valid_indices.sort(key=lambda i: (businessLine[i], code[i]))
59      
60        # Return the sorted list of valid coupon codes
61        return [code[i] for i in valid_indices]
62
1class Solution {
2    /**
3     * Validates and returns sorted coupon codes based on business line and activation status.
4     * 
5     * @param code Array of coupon codes to validate
6     * @param businessLine Array of business lines corresponding to each coupon
7     * @param isActive Array indicating whether each coupon is active
8     * @return List of valid coupon codes sorted by business line and then by code
9     */
10    public List<String> validateCoupons(String[] code, String[] businessLine, boolean[] isActive) {
11        // Store indices of valid coupons
12        List<Integer> validIndices = new ArrayList<>();
13      
14        // Define allowed business lines
15        Set<String> allowedBusinessLines = new HashSet<>(
16            Arrays.asList("electronics", "grocery", "pharmacy", "restaurant")
17        );
18
19        // Iterate through all coupons to find valid ones
20        for (int i = 0; i < code.length; i++) {
21            // Check if coupon is active, business line is allowed, and code format is valid
22            if (isActive[i] && 
23                allowedBusinessLines.contains(businessLine[i]) && 
24                isValidCouponCode(code[i])) {
25                validIndices.add(i);
26            }
27        }
28
29        // Sort valid coupon indices by business line first, then by coupon code
30        validIndices.sort((index1, index2) -> {
31            // Compare business lines alphabetically
32            int businessLineComparison = businessLine[index1].compareTo(businessLine[index2]);
33            if (businessLineComparison != 0) {
34                return businessLineComparison;
35            }
36            // If business lines are the same, compare coupon codes alphabetically
37            return code[index1].compareTo(code[index2]);
38        });
39
40        // Build result list with sorted coupon codes
41        List<String> sortedCouponCodes = new ArrayList<>();
42        for (int index : validIndices) {
43            sortedCouponCodes.add(code[index]);
44        }
45      
46        return sortedCouponCodes;
47    }
48
49    /**
50     * Checks if a coupon code contains only valid characters.
51     * Valid characters are letters, digits, and underscores.
52     * 
53     * @param couponCode The coupon code to validate
54     * @return true if the code is valid, false otherwise
55     */
56    private boolean isValidCouponCode(String couponCode) {
57        // Empty codes are invalid
58        if (couponCode.isEmpty()) {
59            return false;
60        }
61      
62        // Check each character in the code
63        for (char character : couponCode.toCharArray()) {
64            // Code must contain only alphanumeric characters or underscores
65            if (!Character.isLetterOrDigit(character) && character != '_') {
66                return false;
67            }
68        }
69      
70        return true;
71    }
72}
73
1class Solution {
2public:
3    /**
4     * Validates and returns coupon codes that meet specific criteria.
5     * @param code - Vector of coupon codes
6     * @param businessLine - Vector of business line categories for each coupon
7     * @param isActive - Vector indicating whether each coupon is active
8     * @return Vector of valid coupon codes sorted by business line, then by code
9     */
10    vector<string> validateCoupons(vector<string>& code, vector<string>& businessLine, vector<bool>& isActive) {
11        // Store indices of valid coupons
12        vector<int> validIndices;
13      
14        // Define set of valid business lines
15        unordered_set<string> validBusinessLines = {
16            "electronics", 
17            "grocery", 
18            "pharmacy", 
19            "restaurant"
20        };
21
22        // Iterate through all coupons to find valid ones
23        for (int i = 0; i < code.size(); ++i) {
24            const string& couponCode = code[i];
25            const string& business = businessLine[i];
26            bool isActiveCoupon = isActive[i];
27          
28            // Check if coupon meets all validation criteria:
29            // 1. Must be active
30            // 2. Business line must be in the valid set
31            // 3. Coupon code must contain only alphanumeric characters and underscores
32            if (isActiveCoupon && 
33                validBusinessLines.count(business) && 
34                isValidCouponFormat(couponCode)) {
35                validIndices.push_back(i);
36            }
37        }
38
39        // Sort valid coupon indices by business line (primary) and coupon code (secondary)
40        sort(validIndices.begin(), validIndices.end(), [&](int indexA, int indexB) {
41            // First compare by business line
42            if (businessLine[indexA] != businessLine[indexB]) {
43                return businessLine[indexA] < businessLine[indexB];
44            }
45            // If business lines are same, compare by coupon code
46            return code[indexA] < code[indexB];
47        });
48
49        // Build result vector with sorted valid coupon codes
50        vector<string> result;
51        for (int index : validIndices) {
52            result.push_back(code[index]);
53        }
54      
55        return result;
56    }
57
58private:
59    /**
60     * Checks if a coupon code has valid format.
61     * Valid format: non-empty string containing only alphanumeric characters and underscores
62     * @param couponCode - The coupon code string to validate
63     * @return true if valid format, false otherwise
64     */
65    bool isValidCouponFormat(const string& couponCode) {
66        // Empty strings are invalid
67        if (couponCode.empty()) {
68            return false;
69        }
70      
71        // Check each character is alphanumeric or underscore
72        for (char ch : couponCode) {
73            if (!isalnum(ch) && ch != '_') {
74                return false;
75            }
76        }
77      
78        return true;
79    }
80};
81
1/**
2 * Validates and returns coupon codes that meet specific criteria, sorted by business line and code
3 * @param code - Array of coupon codes to validate
4 * @param businessLine - Array of business lines corresponding to each coupon
5 * @param isActive - Array of boolean flags indicating if each coupon is active
6 * @returns Array of valid coupon codes sorted by business line then by code
7 */
8function validateCoupons(code: string[], businessLine: string[], isActive: boolean[]): string[] {
9    // Store indices of valid coupons
10    const validCouponIndices: number[] = [];
11  
12    // Set of allowed business lines
13    const allowedBusinessLines = new Set<string>(['electronics', 'grocery', 'pharmacy', 'restaurant']);
14
15    /**
16     * Checks if a coupon code contains only alphanumeric characters and underscores
17     * @param couponCode - The coupon code to validate
18     * @returns true if the code is valid, false otherwise
19     */
20    const isValidCouponCode = (couponCode: string): boolean => {
21        // Empty codes are invalid
22        if (couponCode.length === 0) {
23            return false;
24        }
25      
26        // Check each character is alphanumeric or underscore
27        for (let i = 0; i < couponCode.length; i++) {
28            const character = couponCode[i];
29            if (!/[a-zA-Z0-9_]/.test(character)) {
30                return false;
31            }
32        }
33      
34        return true;
35    };
36
37    // Filter coupons based on three criteria:
38    // 1. Coupon must be active
39    // 2. Business line must be in the allowed set
40    // 3. Coupon code must contain only valid characters
41    for (let i = 0; i < code.length; i++) {
42        if (isActive[i] && 
43            allowedBusinessLines.has(businessLine[i]) && 
44            isValidCouponCode(code[i])) {
45            validCouponIndices.push(i);
46        }
47    }
48
49    // Sort valid coupon indices by:
50    // 1. Business line (alphabetically)
51    // 2. Coupon code (alphabetically) if business lines are the same
52    validCouponIndices.sort((indexA: number, indexB: number) => {
53        // Compare business lines first
54        if (businessLine[indexA] !== businessLine[indexB]) {
55            return businessLine[indexA] < businessLine[indexB] ? -1 : 1;
56        }
57        // If business lines are same, compare coupon codes
58        return code[indexA] < code[indexB] ? -1 : 1;
59    });
60
61    // Map indices back to their corresponding coupon codes
62    return validCouponIndices.map((index: number) => code[index]);
63}
64

Time and Space Complexity

Time Complexity: O(n Γ— m + n Γ— log n) which simplifies to O(n Γ— log n) when m is considered constant.

The algorithm consists of several operations:

  • The zip operation and iteration through all elements: O(n)
  • For each element, the check function is called which iterates through each character in the code string. If m represents the average length of code strings, this is O(m) per element, resulting in O(n Γ— m) total
  • The in operator for checking membership in the bs set: O(1) per element
  • The sort operation on the filtered indices: O(k Γ— log k) where k is the number of valid coupons. In the worst case, k = n, giving us O(n Γ— log n)
  • The final list comprehension: O(k) which is at most O(n)

The dominant term is O(n Γ— log n) from the sorting operation when the string length m is treated as a constant factor, giving us a final time complexity of O(n Γ— log n).

Space Complexity: O(n)

The space usage includes:

  • The idx list which can store up to n indices in the worst case: O(n)
  • The bs set with 4 constant elements: O(1)
  • The sorting operation may use additional space up to O(n) depending on the implementation
  • The final returned list can have up to n elements: O(n)

Therefore, the overall space complexity is O(n).

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

Common Pitfalls

1. Assuming Business Line Order Matches Alphabetical Order

The current solution relies on the fact that the required business line order ("electronics", "grocery", "pharmacy", "restaurant") happens to match alphabetical order. This is a dangerous assumption that could break if requirements change.

Why it's problematic:

  • If a new business line like "automotive" is added, it would incorrectly appear first
  • If the required order changes (e.g., "restaurant" should come before "pharmacy"), the code would fail silently
  • The code doesn't explicitly enforce the required ordering, making it fragile and unclear

Solution: Create an explicit ordering map for business lines:

def validateCoupons(self, code, businessLine, isActive):
    # Explicit business line ordering
    BUSINESS_ORDER = {
        "electronics": 0,
        "grocery": 1,
        "pharmacy": 2,
        "restaurant": 3
    }
  
    # ... validation code ...
  
    # Sort using explicit ordering
    valid_indices.sort(key=lambda i: (BUSINESS_ORDER[businessLine[i]], code[i]))

2. Not Handling Edge Cases in Code Validation

The validation function might miss certain edge cases or be unclear about what constitutes a valid code.

Potential issues:

  • Does a code with only underscores like "___" count as valid?
  • Should leading/trailing spaces be considered invalid or trimmed?
  • What about codes that are extremely long?

Solution: Add explicit validation rules and document edge cases:

def is_valid_code(coupon_code: str) -> bool:
    # Explicitly handle edge cases
    if not coupon_code or len(coupon_code) > 100:  # Add reasonable length limit
        return False
  
    # Ensure at least one alphanumeric character exists
    has_alphanumeric = False
    for char in coupon_code:
        if char.isalpha() or char.isdigit():
            has_alphanumeric = True
        elif char != "_":
            return False
  
    return has_alphanumeric  # Reject codes with only underscores

3. Performance Issues with Large Datasets

The current character-by-character validation could be inefficient for very long coupon codes or large datasets.

Solution: Use regex for more efficient validation:

import re

class Solution:
    def __init__(self):
        # Compile regex once for reuse
        self.valid_code_pattern = re.compile(r'^[a-zA-Z0-9_]+$')
  
    def is_valid_code(self, coupon_code: str) -> bool:
        return bool(coupon_code and self.valid_code_pattern.match(coupon_code))

This approach is generally faster for longer strings and provides clearer intent about what constitutes a valid code.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More