3606. Coupon Code Validator
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 identifierbusinessLine[i]
: a string denoting the coupon's business categoryisActive[i]
: a boolean indicating if the coupon is currently active
A coupon is considered valid when it meets all three conditions:
- Its
code
is non-empty and contains only alphanumeric characters (a-z, A-Z, 0-9) and underscores (_
) - Its
businessLine
is exactly one of these four categories:"electronics"
,"grocery"
,"pharmacy"
, or"restaurant"
- Its
isActive
status istrue
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.
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) isTrue
b
(businessLine) is in our valid categories setc
(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 EvaluatorExample 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:
- First by business line: "electronics" < "grocery" < "pharmacy"
- 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. Ifm
represents the average length of code strings, this isO(m)
per element, resulting inO(n Γ m)
total - The
in
operator for checking membership in thebs
set:O(1)
per element - The
sort
operation on the filtered indices:O(k Γ log k)
wherek
is the number of valid coupons. In the worst case,k = n
, giving usO(n Γ log n)
- The final list comprehension:
O(k)
which is at mostO(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 ton
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.
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Donβt Miss This!