Facebook Pixel

1357. Apply Discount Every n Orders

MediumDesignArrayHash Table
Leetcode Link

Problem Description

You need to implement a cashier system for a supermarket that tracks products and applies discounts to certain customers.

The supermarket has products with unique IDs and corresponding prices. These are given as two arrays:

  • products[i] represents the ID of the i-th product
  • prices[i] represents the price of the i-th product

When customers check out, their purchases are represented by:

  • product[j] - the ID of the j-th product they're buying
  • amount[j] - the quantity of the j-th product they're buying

The subtotal is calculated by summing up amount[j] * price for each product.

The supermarket runs a special promotion where every n-th customer receives a percentage discount. For example, if n = 3 and discount = 50, then the 3rd, 6th, 9th, etc. customers get 50% off their subtotal.

You need to implement a Cashier class with two methods:

  1. Constructor Cashier(n, discount, products, prices): Initializes the cashier system with:

    • n: Every n-th customer gets the discount
    • discount: The discount percentage (0-100)
    • products and prices: Arrays defining the store's inventory
  2. getBill (product, amount): Calculates and returns the final bill for a customer:

    • Takes arrays of product IDs and their quantities
    • Applies the discount if this is the n-th customer
    • Returns the final total as a float
    • The formula for discount is: final_price = subtotal * (100 - discount) / 100

The system needs to keep track of how many customers have been served to determine who gets the discount. Answers within 10^-5 of the actual value are accepted.

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

Intuition

The key insight is that we need to efficiently look up product prices and keep track of customer count to apply discounts at the right time.

Since we need to frequently look up prices by product ID when calculating bills, a hash table (dictionary) is the natural choice. This gives us O(1) price lookups instead of searching through the products array each time.

For tracking which customer gets the discount, we need a counter that increments with each customer. When counter % n == 0, we know this customer should receive the discount. This is a simple modulo operation that tells us if the current customer number is divisible by n.

The calculation flow becomes straightforward:

  1. Increment the customer counter
  2. Check if this customer gets a discount using counter % n
  3. Calculate the subtotal by looking up each product's price in our hash table and multiplying by quantity
  4. If there's a discount, apply it to each item as we calculate

The discount calculation can be done in two ways:

  • Calculate the full subtotal first, then apply discount: subtotal * (100 - discount) / 100
  • Apply discount to each item individually: price * amount * (100 - discount) / 100

Both approaches work, but applying the discount item by item as shown in the solution allows us to combine the calculation in a single pass through the products. The formula x - (discount * x) / 100 is mathematically equivalent to x * (100 - discount) / 100, where x is the item's cost before discount.

Solution Approach

The solution uses a hash table combined with simulation to efficiently handle product lookups and customer tracking.

Data Structure Setup:

  • Create a hash table d that maps each product ID to its price: {product_id: price}
  • Initialize a counter i to track the number of customers served
  • Store the discount parameters n and discount for later use

Implementation Details:

  1. Constructor (__init__):

    • Set self.i = 0 to track customer count
    • Store n and discount values
    • Build the hash table using dictionary comprehension: self.d = {product: price for product, price in zip(products, prices)}
    • The zip function pairs each product ID with its corresponding price
  2. Bill Calculation (getBill):

    • Increment the customer counter: self.i += 1
    • Determine if current customer gets discount: discount = self.discount if self.i % self.n == 0 else 0
      • If i % n == 0, this is the n-th customer and gets the discount
      • Otherwise, discount is 0
    • Calculate the total bill:
      ans = 0
      for p, a in zip(product, amount):
          x = self.d[p] * a  # price * quantity
          ans += x - (discount * x) / 100  # apply discount if any
    • The formula x - (discount * x) / 100 calculates the discounted price
      • When discount = 0: result is x (no discount)
      • When discount > 0: result is x * (1 - discount/100) (discounted price)

Time Complexity:

  • Constructor: O(m) where m is the number of products
  • getBill: O(k) where k is the number of items in the current purchase

Space Complexity:

  • O(m) to store the hash table of products and prices

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate how the cashier system works.

Setup:

  • Initialize: Cashier(n=3, discount=50, products=[1,2,3,4,5], prices=[100,200,300,400,500])
  • This creates a hash table: {1:100, 2:200, 3:300, 4:400, 5:500}
  • Customer counter starts at i=0
  • Every 3rd customer gets 50% off

Customer 1:

  • Call: getBill([1,2], [1,2])
  • Counter increments: i=1
  • Check discount: 1 % 3 = 1 (not divisible by 3, no discount)
  • Calculate bill:
    • Product 1: 100 * 1 = 100
    • Product 2: 200 * 2 = 400
    • Total: 100 + 400 = 500.0

Customer 2:

  • Call: getBill([3,5], [2,1])
  • Counter increments: i=2
  • Check discount: 2 % 3 = 2 (no discount)
  • Calculate bill:
    • Product 3: 300 * 2 = 600
    • Product 5: 500 * 1 = 500
    • Total: 600 + 500 = 1100.0

Customer 3 (Gets Discount!):

  • Call: getBill([1,2], [1,2])
  • Counter increments: i=3
  • Check discount: 3 % 3 = 0 (divisible by 3, apply 50% discount)
  • Calculate bill with discount:
    • Product 1: 100 * 1 = 100, after discount: 100 - (50 * 100)/100 = 50
    • Product 2: 200 * 2 = 400, after discount: 400 - (50 * 400)/100 = 200
    • Total: 50 + 200 = 250.0 (half of the original 500)

Customer 4:

  • Call: getBill([4], [3])
  • Counter increments: i=4
  • Check discount: 4 % 3 = 1 (no discount)
  • Calculate bill:
    • Product 4: 400 * 3 = 1200
    • Total: 1200.0

Customer 6 (Gets Discount!):

  • Would get 50% off since 6 % 3 = 0

This example demonstrates how the hash table provides instant price lookups and the modulo operation efficiently determines which customers receive discounts.

Solution Implementation

1from typing import List
2
3class Cashier:
4    def __init__(self, n: int, discount: int, products: List[int], prices: List[int]):
5        """
6        Initialize the Cashier system.
7      
8        Args:
9            n: Every nth customer will receive the discount
10            discount: Discount percentage to apply
11            products: List of product IDs
12            prices: List of corresponding product prices
13        """
14        # Counter to track the number of customers
15        self.customer_count = 0
16        # Every nth customer gets a discount
17        self.n = n
18        # Discount percentage
19        self.discount = discount
20        # Dictionary mapping product ID to its price
21        self.product_price_map = {product_id: price 
22                                  for product_id, price in zip(products, prices)}
23
24    def getBill(self, product: List[int], amount: List[int]) -> float:
25        """
26        Calculate the bill for a customer's purchase.
27      
28        Args:
29            product: List of product IDs being purchased
30            amount: List of quantities for each product
31          
32        Returns:
33            Total bill amount after applying discount (if applicable)
34        """
35        # Increment customer counter
36        self.customer_count += 1
37      
38        # Check if current customer is eligible for discount (every nth customer)
39        discount_percentage = self.discount if self.customer_count % self.n == 0 else 0
40      
41        # Calculate total bill
42        total_bill = 0.0
43        for product_id, quantity in zip(product, amount):
44            # Calculate subtotal for this product
45            subtotal = self.product_price_map[product_id] * quantity
46            # Apply discount if eligible and add to total
47            total_bill += subtotal - (discount_percentage * subtotal) / 100
48          
49        return total_bill
50
51
52# Your Cashier object will be instantiated and called as such:
53# obj = Cashier(n, discount, products, prices)
54# param_1 = obj.getBill(product, amount)
55
1class Cashier {
2    // Counter to track the number of customers
3    private int customerCount;
4    // Every nth customer gets a discount
5    private int n;
6    // Discount percentage to apply
7    private int discountPercentage;
8    // Map to store product ID to price mapping
9    private Map<Integer, Integer> productPriceMap = new HashMap<>();
10
11    /**
12     * Constructor to initialize the cashier system
13     * @param n - Every nth customer receives a discount
14     * @param discount - The discount percentage to apply
15     * @param products - Array of product IDs
16     * @param prices - Array of corresponding product prices
17     */
18    public Cashier(int n, int discount, int[] products, int[] prices) {
19        this.n = n;
20        this.discountPercentage = discount;
21      
22        // Build the product-price mapping
23        for (int i = 0; i < products.length; i++) {
24            productPriceMap.put(products[i], prices[i]);
25        }
26    }
27
28    /**
29     * Calculate the bill for a customer's purchase
30     * @param product - Array of product IDs being purchased
31     * @param amount - Array of quantities for each product
32     * @return The total bill amount after applying discount if applicable
33     */
34    public double getBill(int[] product, int[] amount) {
35        // Increment customer count and check if this customer gets a discount
36        customerCount++;
37        int applicableDiscount = (customerCount % n == 0) ? discountPercentage : 0;
38      
39        double totalBill = 0.0;
40      
41        // Calculate the total cost for each product
42        for (int i = 0; i < product.length; i++) {
43            int productId = product[i];
44            int quantity = amount[i];
45          
46            // Calculate subtotal for this product
47            int subtotal = productPriceMap.get(productId) * quantity;
48          
49            // Apply discount if applicable and add to total bill
50            totalBill += subtotal - (applicableDiscount * subtotal) / 100.0;
51        }
52      
53        return totalBill;
54    }
55}
56
57/**
58 * Your Cashier object will be instantiated and called as such:
59 * Cashier obj = new Cashier(n, discount, products, prices);
60 * double param_1 = obj.getBill(product,amount);
61 */
62
1class Cashier {
2public:
3    /**
4     * Constructor to initialize the cashier system
5     * @param n: Every nth customer gets a discount
6     * @param discount: Discount percentage for every nth customer
7     * @param products: Vector of product IDs
8     * @param prices: Vector of product prices corresponding to product IDs
9     */
10    Cashier(int n, int discount, vector<int>& products, vector<int>& prices) {
11        this->nthCustomer = n;
12        this->discountPercent = discount;
13      
14        // Build product ID to price mapping
15        for (int i = 0; i < products.size(); ++i) {
16            productPriceMap[products[i]] = prices[i];
17        }
18    }
19
20    /**
21     * Calculate the bill for current customer
22     * @param product: Vector of product IDs being purchased
23     * @param amount: Vector of quantities for each product
24     * @return: Total bill amount after applying discount (if applicable)
25     */
26    double getBill(vector<int> product, vector<int> amount) {
27        // Increment customer counter and check if current customer gets discount
28        customerCount++;
29        int currentDiscount = (customerCount % nthCustomer == 0) ? discountPercent : 0;
30      
31        double totalBill = 0.0;
32      
33        // Calculate total bill for all products
34        for (int i = 0; i < product.size(); ++i) {
35            // Calculate cost for current product (price * quantity)
36            int productCost = productPriceMap[product[i]] * amount[i];
37          
38            // Apply discount if applicable and add to total
39            totalBill += productCost - (currentDiscount * productCost) / 100.0;
40        }
41      
42        return totalBill;
43    }
44
45private:
46    int customerCount = 0;                          // Counter for number of customers served
47    int nthCustomer;                                // Every nth customer gets discount
48    int discountPercent;                            // Discount percentage to apply
49    unordered_map<int, int> productPriceMap;        // Map from product ID to price
50};
51
52/**
53 * Your Cashier object will be instantiated and called as such:
54 * Cashier* obj = new Cashier(n, discount, products, prices);
55 * double param_1 = obj->getBill(product,amount);
56 */
57
1// Counter for number of customers served
2let customerCount: number = 0;
3// Every nth customer gets discount
4let nthCustomer: number;
5// Discount percentage to apply
6let discountPercent: number;
7// Map from product ID to price
8let productPriceMap: Map<number, number> = new Map();
9
10/**
11 * Initialize the cashier system
12 * @param n - Every nth customer gets a discount
13 * @param discount - Discount percentage for every nth customer
14 * @param products - Array of product IDs
15 * @param prices - Array of product prices corresponding to product IDs
16 */
17function Cashier(n: number, discount: number, products: number[], prices: number[]): void {
18    // Reset customer count for new cashier instance
19    customerCount = 0;
20    nthCustomer = n;
21    discountPercent = discount;
22  
23    // Clear and rebuild product ID to price mapping
24    productPriceMap.clear();
25    for (let i = 0; i < products.length; i++) {
26        productPriceMap.set(products[i], prices[i]);
27    }
28}
29
30/**
31 * Calculate the bill for current customer
32 * @param product - Array of product IDs being purchased
33 * @param amount - Array of quantities for each product
34 * @returns Total bill amount after applying discount (if applicable)
35 */
36function getBill(product: number[], amount: number[]): number {
37    // Increment customer counter and check if current customer gets discount
38    customerCount++;
39    const currentDiscount: number = (customerCount % nthCustomer === 0) ? discountPercent : 0;
40  
41    let totalBill: number = 0.0;
42  
43    // Calculate total bill for all products
44    for (let i = 0; i < product.length; i++) {
45        // Calculate cost for current product (price * quantity)
46        const productPrice: number = productPriceMap.get(product[i]) || 0;
47        const productCost: number = productPrice * amount[i];
48      
49        // Apply discount if applicable and add to total
50        totalBill += productCost - (currentDiscount * productCost) / 100.0;
51    }
52  
53    return totalBill;
54}
55

Time and Space Complexity

Time Complexity:

  • Initialization (__init__): O(n), where n is the number of products. This is because creating the dictionary d requires iterating through the products and prices lists once using zip(), which takes linear time proportional to the number of products.

  • getBill method: O(m), where m is the number of products being purchased (length of the product list in the method call). The method iterates through the product and amount lists once using zip(), and for each item, it performs constant time operations (dictionary lookup, multiplication, and arithmetic operations).

Space Complexity:

  • O(n), where n is the number of products. The space is primarily consumed by the dictionary d that stores the mapping of all products to their prices. The dictionary contains n key-value pairs. Other variables (i, n, discount) use constant space O(1).

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

Common Pitfalls

1. Integer Division vs Float Division

A common mistake is using integer division when calculating the discount, which can lead to incorrect results in languages that distinguish between integer and float division.

Pitfall Example:

# Wrong - in Python 2 or some other languages, this could cause integer division
total_bill += subtotal - (discount_percentage * subtotal) // 100  # Using // instead of /

Solution: Always use float division and ensure the result is a float:

# Correct
total_bill += subtotal - (discount_percentage * subtotal) / 100
# Or explicitly convert to float
total_bill += subtotal * (100 - discount_percentage) / 100.0

2. Customer Counter Reset

Some implementations might accidentally reset the customer counter or fail to properly maintain state between calls.

Pitfall Example:

def getBill(self, product: List[int], amount: List[int]) -> float:
    customer_count = 0  # Wrong - creates local variable instead of using instance variable
    customer_count += 1
    # This will always treat every customer as the first customer

Solution: Always use the instance variable with self:

def getBill(self, product: List[int], amount: List[int]) -> float:
    self.customer_count += 1  # Correct - modifies instance variable

3. Incorrect Discount Formula Application

Applying the discount formula incorrectly, especially when mixing the percentage calculation.

Pitfall Example:

# Wrong - applies discount to each item individually before summing
for product_id, quantity in zip(product, amount):
    price = self.product_price_map[product_id]
    if self.customer_count % self.n == 0:
        price = price * (100 - self.discount) / 100  # Modifying price directly
    total_bill += price * quantity

Solution: Calculate the subtotal first, then apply discount to the total:

# Better approach - clearer logic
subtotal = sum(self.product_price_map[pid] * qty for pid, qty in zip(product, amount))
if self.customer_count % self.n == 0:
    return subtotal * (100 - self.discount) / 100
return subtotal

4. Missing Product ID in Hash Table

Not handling the case where a product ID might not exist in the hash table (though the problem assumes all product IDs are valid).

Pitfall Example:

# Could raise KeyError if product_id not in dictionary
subtotal = self.product_price_map[product_id] * quantity

Solution: Add defensive programming if needed:

# Defensive approach
subtotal = self.product_price_map.get(product_id, 0) * quantity
# Or with error handling
if product_id not in self.product_price_map:
    raise ValueError(f"Product {product_id} not found")

5. Precision Issues with Float Arithmetic

When dealing with money calculations, floating-point arithmetic can introduce precision errors.

Pitfall Example:

# Multiple float operations can accumulate rounding errors
for product_id, quantity in zip(product, amount):
    subtotal = self.product_price_map[product_id] * quantity
    discounted = subtotal - (discount_percentage * subtotal) / 100
    total_bill += discounted  # Accumulating potential rounding errors

Solution: Use the Decimal module for precise monetary calculations if needed:

from decimal import Decimal, ROUND_HALF_UP

# For precise monetary calculations
subtotal = Decimal(str(price)) * Decimal(str(quantity))
discount_amount = (subtotal * Decimal(str(discount_percentage)) / Decimal('100'))
final_amount = subtotal - discount_amount
# Round to 2 decimal places for currency
final_amount = final_amount.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

However, since the problem accepts answers within 10^-5 of the actual value, regular float arithmetic is sufficient for this specific case.

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

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

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

Load More