Facebook Pixel

2288. Apply Discount to Prices

MediumString
Leetcode Link

Problem Description

You are given a sentence that consists of words separated by single spaces. Each word can contain digits, lowercase letters, and the dollar sign '$'.

A word is considered a price if it:

  • Starts with a dollar sign '$'
  • Is followed by only digits (no other characters)

For example:

  • "$100", "$23", "$6" are valid prices
  • "100" (no dollar sign), "$" (no digits), "$1e5" (contains non-digit character 'e') are NOT valid prices

Your task is to:

  1. Find all words in the sentence that represent prices
  2. Apply a given discount percentage to each price
  3. Format the discounted prices to show exactly two decimal places
  4. Return the modified sentence with the updated prices

The input consists of:

  • sentence: a string containing space-separated words
  • discount: an integer representing the discount percentage to apply

For each price found, you need to calculate: new_price = original_price * (1 - discount/100) and format it with exactly two decimal places.

For example, if the sentence is "there are $1 $2 and 5$ candies in the shop" and discount is 50:

  • "$1" becomes "$0.50" (50% off of $1)
  • "$2" becomes "$1.00" (50% off of $2)
  • "5$" remains unchanged (dollar sign must be at the beginning)
  • The result would be "there are $0.50 $1.00 and 5$ candies in the shop"

All prices are guaranteed to have at most 10 digits.

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

Intuition

The problem is essentially asking us to find and replace specific patterns in a string. We need to identify words that are valid prices and transform them.

Since the sentence is already space-separated, the natural approach is to split it into individual words and process each word independently. This way, we can examine each word to determine if it's a price or not.

For each word, we need to check if it's a valid price. A valid price has two characteristics:

  1. It must start with '$'
  2. Everything after the '$' must be digits only

If we find a valid price, we need to:

  1. Extract the numeric value (everything after the '$')
  2. Apply the discount formula: new_price = original_price * (1 - discount/100)
  3. Format it back as a price string with exactly 2 decimal places

The key insight is that we can use Python's string methods to make this check simple:

  • w[0] == '$' checks if the first character is a dollar sign
  • w[1:].isdigit() checks if all remaining characters are digits
  • Python's f-string formatting f'${value:.2f}' handles the decimal place formatting automatically

After processing each word (either keeping it as-is or replacing it with the discounted price), we join all words back together with spaces to form the final sentence.

This approach is straightforward because we're working with the natural structure of the input - words separated by spaces - rather than trying to use complex pattern matching or regular expressions.

Solution Approach

The solution follows a simulation approach where we process each word in the sentence individually.

Step 1: Split the sentence into words We use sentence.split() to break the sentence into an array of words separated by spaces. This gives us a list where each element is a word that we can examine.

Step 2: Process each word We iterate through each word w in the split sentence and check if it represents a valid price:

  • Check if the first character is '$': w[0] == '$'
  • Check if all remaining characters are digits: w[1:].isdigit()

Both conditions must be true for a word to be considered a valid price.

Step 3: Apply discount to valid prices When we find a valid price:

  1. Extract the numeric value using int(w[1:]) - this converts the string after '$' to an integer
  2. Calculate the discounted price: int(w[1:]) * (1 - discount / 100)
  3. Format the result with exactly 2 decimal places using f-string: f'${value:.2f}'

The complete transformation becomes: f'${int(w[1:]) * (1 - discount / 100):.2f}'

Step 4: Build the result We collect all words (both modified prices and unchanged words) in a list ans. Words that aren't valid prices are added unchanged, while valid prices are replaced with their discounted versions.

Step 5: Join the words back Finally, we use ' '.join(ans) to combine all words back into a single string with spaces between them.

Time Complexity: O(n) where n is the length of the sentence, as we process each character once.

Space Complexity: O(n) to store the split words and the result list.

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 the solution approach.

Input:

  • sentence = "the item costs $25 or $3 but not 15$ dollars"
  • discount = 20

Step 1: Split the sentence into words

words = ["the", "item", "costs", "$25", "or", "$3", "but", "not", "15$", "dollars"]

Step 2-4: Process each word and build result

Let's examine each word:

  1. "the" - First character is 't', not '$' → Not a price → Keep as "the"

  2. "item" - First character is 'i', not '$' → Not a price → Keep as "item"

  3. "costs" - First character is 'c', not '$' → Not a price → Keep as "costs"

  4. "$25" - First character is '$' ✓, remaining "25" is all digits ✓ → Valid price!

    • Extract value: 25
    • Apply 20% discount: 25 × (1 - 20/100) = 25 × 0.8 = 20.0
    • Format: "$20.00"
  5. "or" - First character is 'o', not '$' → Not a price → Keep as "or"

  6. "$3" - First character is '$' ✓, remaining "3" is all digits ✓ → Valid price!

    • Extract value: 3
    • Apply 20% discount: 3 × (1 - 20/100) = 3 × 0.8 = 2.4
    • Format: "$2.40"
  7. "but" - First character is 'b', not '$' → Not a price → Keep as "but"

  8. "not" - First character is 'n', not '$' → Not a price → Keep as "not"

  9. "15$" - First character is '1', not 'NotapriceKeepas"15' → Not a price → Keep as "15" (Note: Dollar sign at the end doesn't count!)

  10. "dollars" - First character is 'd', not '$' → Not a price → Keep as "dollars"

Step 5: Join the processed words

result = ["the", "item", "costs", "$20.00", "or", "$2.40", "but", "not", "15$", "dollars"]

Join with spaces: "the item costs $20.00 or $2.40 but not 15$ dollars"

Final Output: "the item costs $20.00 or $2.40 but not 15$ dollars"

The algorithm correctly identified only "$25" and "$3" as valid prices, applied the 20% discount to each, and formatted them with exactly two decimal places while leaving all other words unchanged.

Solution Implementation

1class Solution:
2    def discountPrices(self, sentence: str, discount: int) -> str:
3        """
4        Apply discount to all valid prices in a sentence.
5        A valid price starts with '$' followed by digits only.
6      
7        Args:
8            sentence: Input string containing words separated by spaces
9            discount: Discount percentage to apply (0-100)
10      
11        Returns:
12            Modified sentence with discounted prices formatted to 2 decimal places
13        """
14        # List to store processed words
15        result_words = []
16      
17        # Process each word in the sentence
18        for word in sentence.split():
19            # Check if word is a valid price format:
20            # - Starts with '$'
21            # - Has at least one character after '$'
22            # - All characters after '$' are digits
23            if len(word) > 1 and word[0] == '$' and word[1:].isdigit():
24                # Extract the numeric value after '$'
25                original_price = int(word[1:])
26              
27                # Calculate discounted price
28                discount_multiplier = 1 - discount / 100
29                discounted_price = original_price * discount_multiplier
30              
31                # Format the new price with '$' prefix and 2 decimal places
32                word = f'${discounted_price:.2f}'
33          
34            # Add the processed word to result
35            result_words.append(word)
36      
37        # Join all words back into a sentence with spaces
38        return ' '.join(result_words)
39
1class Solution {
2    /**
3     * Applies a discount to all valid prices in a sentence.
4     * A valid price starts with '$' followed by digits only.
5     * 
6     * @param sentence The input sentence containing words separated by spaces
7     * @param discount The discount percentage to apply (0-100)
8     * @return The sentence with discounted prices formatted to 2 decimal places
9     */
10    public String discountPrices(String sentence, int discount) {
11        // Split the sentence into individual words
12        String[] words = sentence.split(" ");
13      
14        // Process each word in the sentence
15        for (int i = 0; i < words.length; i++) {
16            // Check if the current word is a valid price format
17            if (isValidPrice(words[i])) {
18                // Extract the numeric value (remove the '$' prefix)
19                long priceValue = Long.parseLong(words[i].substring(1));
20              
21                // Calculate the discounted price
22                // Convert discount from percentage to decimal (e.g., 50 -> 0.5)
23                double discountedPrice = priceValue * (1 - discount / 100.0);
24              
25                // Format the discounted price with '$' prefix and 2 decimal places
26                words[i] = String.format("$%.2f", discountedPrice);
27            }
28        }
29      
30        // Join all words back into a sentence with space delimiter
31        return String.join(" ", words);
32    }
33
34    /**
35     * Validates if a string represents a valid price format.
36     * Valid format: starts with '$' followed by one or more digits.
37     * 
38     * @param word The string to validate
39     * @return true if the string is a valid price format, false otherwise
40     */
41    private boolean isValidPrice(String word) {
42        // Check if the string starts with '$' and has more than one character
43        if (word.charAt(0) != '$' || word.length() == 1) {
44            return false;
45        }
46      
47        // Verify all characters after '$' are digits
48        for (int i = 1; i < word.length(); i++) {
49            if (!Character.isDigit(word.charAt(i))) {
50                return false;
51            }
52        }
53      
54        return true;
55    }
56}
57
1class Solution {
2public:
3    string discountPrices(string sentence, int discount) {
4        // Create input string stream to parse words from sentence
5        istringstream inputStream(sentence);
6        string word;
7        string result;
8      
9        // Lambda function to validate if a word is a valid price format
10        // Valid price: starts with '$' followed by one or more digits only
11        auto isValidPrice = [](const string& str) {
12            // Check if string starts with '$' and has at least one digit after it
13            if (str[0] != '$' || str.size() == 1) {
14                return false;
15            }
16          
17            // Verify all characters after '$' are digits
18            for (int i = 1; i < str.size(); ++i) {
19                if (!isdigit(str[i])) {
20                    return false;
21                }
22            }
23            return true;
24        };
25      
26        // Process each word in the sentence
27        while (inputStream >> word) {
28            if (isValidPrice(word)) {
29                // Extract numeric value after '$' and apply discount
30                // Convert to cents to avoid floating point precision issues
31                long long priceInCents = stoll(word.substr(1)) * (100 - discount);
32              
33                // Format the discounted price with exactly 2 decimal places
34                char formattedPrice[20];
35                sprintf(formattedPrice, "$%lld.%02lld", 
36                        priceInCents / 100,    // Dollar part
37                        priceInCents % 100);    // Cents part
38              
39                result += formattedPrice;
40            } else {
41                // Keep non-price words unchanged
42                result += word;
43            }
44          
45            // Add space separator after each word
46            result += ' ';
47        }
48      
49        // Remove the trailing space from the final result
50        result.pop_back();
51      
52        return result;
53    }
54};
55
1/**
2 * Applies a discount to all valid price tags in a sentence
3 * @param sentence - The input sentence containing words and price tags
4 * @param discount - The discount percentage to apply (0-100)
5 * @returns The sentence with discounted prices formatted to 2 decimal places
6 */
7function discountPrices(sentence: string, discount: number): string {
8    // Calculate the multiplier after applying the discount
9    const discountMultiplier: number = (100 - discount) / 100;
10  
11    // Regular expression to match valid price format: $X where X is a positive number
12    // Pattern breakdown:
13    // ^ - Start of string
14    // (\$) - Capture group 1: Dollar sign
15    // (([1-9]\d*\.?\d*)|(0\.\d*)) - Capture group 2: Valid price number
16    //   - [1-9]\d*\.?\d* - Number starting with 1-9, optional decimal part
17    //   - 0\.\d* - Number starting with 0 only if followed by decimal
18    // $ - End of string
19    const pricePattern: RegExp = /^(\$)(([1-9]\d*\.?\d*)|(0\.\d*))$/;
20  
21    // Process each word in the sentence
22    const processedWords: string[] = sentence.split(' ').map((word: string) => {
23        // Check if the word matches the price pattern
24        if (!pricePattern.test(word)) {
25            return word; // Return unchanged if not a valid price
26        }
27      
28        // Reset regex lastIndex for next test (important for global flag)
29        pricePattern.lastIndex = 0;
30      
31        // Replace the price with discounted value
32        return word.replace(pricePattern, (match: string, dollarSign: string, priceValue: string) => {
33            // Calculate discounted price and format to 2 decimal places
34            const discountedPrice: number = discountMultiplier * parseFloat(priceValue);
35            return `$${discountedPrice.toFixed(2)}`;
36        });
37    });
38  
39    // Join the processed words back into a sentence
40    return processedWords.join(' ');
41}
42

Time and Space Complexity

The time complexity is O(n), where n is the length of the string sentence. This is because:

  • The split() operation traverses the entire string once: O(n)
  • The loop iterates through each word, and in total, all words combined have length proportional to n
  • Inside the loop, checking w[0] == '$' is O(1), and w[1:].isdigit() is O(k) where k is the length of the word
  • String formatting and arithmetic operations are O(k) for each word
  • The join() operation at the end is O(n)
  • Since the sum of all word lengths is bounded by n, the overall time complexity is O(n)

The space complexity is O(n), where n is the length of the string sentence. This is because:

  • The split() operation creates a list of words that collectively take up O(n) space
  • The ans list stores modified words, which also takes O(n) space in total
  • The final joined string returned by join() takes O(n) space
  • Therefore, the overall space complexity is O(n)

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

Common Pitfalls

1. Incorrect Price Validation Logic

A critical pitfall is incorrectly validating what constitutes a valid price. Many developers make these mistakes:

Pitfall Example:

# WRONG: Forgetting to check if there are digits after '$'
if word[0] == '$' and word[1:].isdigit():
    # This crashes when word is just "$" (IndexError)

Another Common Mistake:

# WRONG: Not checking for empty string after '$'
if word.startswith('$') and word[1:].isdigit():
    # This returns True for "$" because "".isdigit() returns False, 
    # but we never checked if word[1:] is empty

Solution: Always verify the word has sufficient length before accessing indices:

if len(word) > 1 and word[0] == '$' and word[1:].isdigit():
    # Correct: ensures word has at least 2 characters

2. Integer Overflow with Large Prices

Pitfall Example:

# Potential issue with very large numbers
original_price = int(word[1:])  # Could be up to 10 digits

While Python handles large integers well, in other languages or when converting this logic, you might encounter overflow issues. Additionally, intermediate calculations could lose precision.

Solution: Use float conversion directly for better precision handling:

original_price = float(word[1:])
discounted_price = original_price * (1 - discount / 100)

3. Floating Point Precision Errors

Pitfall Example:

# May produce unexpected results due to floating point arithmetic
discounted_price = int(word[1:]) * (1 - discount / 100)
# For $100 with 33% discount, might get 66.99999999999999 instead of 67.00

Solution: The formatting with .2f handles this correctly, but be aware that intermediate calculations might have precision issues. The current solution handles this appropriately with the f-string formatting.

4. Edge Cases with Single Character Words

Pitfall Example:

# Forgetting to handle single '$' character
if word[0] == '$' and word[1:].isdigit():  # IndexError if word is just "$"

Solution: Always check length first:

if len(word) > 1 and word[0] == '$' and word[1:].isdigit():

5. Incorrect String Splitting or Joining

Pitfall Example:

# WRONG: Using wrong delimiter
words = sentence.split(',')  # Should be split by spaces, not commas

# WRONG: Joining without spaces
return ''.join(result_words)  # Words will be concatenated without spaces

Solution: Use the correct delimiters:

words = sentence.split()  # Splits by any whitespace
return ' '.join(result_words)  # Joins with single space

Complete Robust Solution:

class Solution:
    def discountPrices(self, sentence: str, discount: int) -> str:
        result_words = []
      
        for word in sentence.split():
            # Comprehensive validation:
            # 1. Length check prevents IndexError
            # 2. First character must be '$'
            # 3. Must have digits after '$'
            # 4. All characters after '$' must be digits
            if (len(word) > 1 and 
                word[0] == '$' and 
                word[1:].isdigit()):
              
                # Safe conversion and calculation
                original_price = float(word[1:])
                discounted_price = original_price * (1 - discount / 100)
              
                # Proper formatting ensures exactly 2 decimal places
                word = f'${discounted_price:.2f}'
          
            result_words.append(word)
      
        return ' '.join(result_words)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

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

Load More