LeetCode 1357 - Apply Discount Every n Orders
The problem describes a supermarket that sells products identified by unique integer IDs. Each product has a corresponding price. Customers purchase products in certain amounts, generating a bill that is the sum of the prices multiplied by the amounts purchased.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Design
Solution
Problem Understanding
The problem describes a supermarket that sells products identified by unique integer IDs. Each product has a corresponding price. Customers purchase products in certain amounts, generating a bill that is the sum of the prices multiplied by the amounts purchased. The supermarket offers a periodic discount: every nth customer gets a percentage discount off their subtotal.
The input consists of the parameters used to initialize a Cashier object: n (the interval for the discount), discount (percentage off), products (list of product IDs), and prices (list of prices corresponding to the product IDs). Then, a series of calls to getBill(product, amount) simulate customers checking out. The output is the final amount the customer pays, which applies the discount only if the customer is the nth one.
Constraints ensure that the product IDs are unique, purchase amounts are valid, and the number of calls is manageable. This guarantees that we do not have to handle invalid product IDs or duplicate products within a single bill, simplifying our implementation. Edge cases include zero discount, discount applied on the first customer if n=1, and handling multiple sequential calls correctly.
Approaches
The naive or brute-force approach would compute the subtotal by iterating through the purchased products for each getBill call. For every product in the product array, the code would search through the products array to find the corresponding price, multiply by the amount, sum the results, and then check if a discount applies. While correct, this approach requires a linear search for every product, making it inefficient if products is large.
The optimal approach leverages a hash map (dictionary) to map product IDs to prices during initialization. This allows for O(1) price lookups per product, avoiding repeated linear searches. A simple counter keeps track of the number of customers to determine when to apply the discount. This results in a time-efficient solution with straightforward logic.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(m * k) | O(1) | m = length of product array, k = length of products array; searches linearly for prices |
| Optimal | O(m) | O(p) | p = number of products; uses hash map for constant-time price lookups |
Algorithm Walkthrough
- Initialize Cashier Object: Store the discount interval
nand discount percentagediscount. Build a hash mapprice_mapthat maps each product ID to its price for O(1) lookups. Initialize a customer countercustomer_countto 0. - Compute Bill in getBill: For each call to
getBill, increment the customer counter by 1. - Calculate Subtotal: Iterate over the purchased
productIDs. For each product ID, multiply the correspondingamountby the price retrieved fromprice_mapand add it to a running subtotal. - Apply Discount If Applicable: If
customer_countis divisible byn, multiply the subtotal by(100 - discount) / 100to apply the discount. Otherwise, the subtotal remains unchanged. - Return Final Bill: Return the resulting total as a floating-point number.
Why it works: The hash map ensures constant-time price lookup, so we avoid repeated searches. Incrementing the counter guarantees that the discount is applied exactly every nth customer. The algorithm correctly handles any number of calls in sequence and applies the discount accurately based on the customer order.
Python Solution
from typing import List
class Cashier:
def __init__(self, n: int, discount: int, products: List[int], prices: List[int]):
self.n = n
self.discount = discount
self.price_map = {prod: price for prod, price in zip(products, prices)}
self.customer_count = 0
def getBill(self, product: List[int], amount: List[int]) -> float:
self.customer_count += 1
subtotal = 0
for prod_id, amt in zip(product, amount):
subtotal += self.price_map[prod_id] * amt
if self.customer_count % self.n == 0:
subtotal *= (100 - self.discount) / 100
return subtotal
In this Python implementation, we first create a mapping of product IDs to their prices for quick lookups. Each call to getBill updates the customer count and calculates the subtotal using this mapping. The discount is conditionally applied based on the counter, and the final subtotal is returned as a floating-point number, which meets the precision requirement of 10^-5.
Go Solution
type Cashier struct {
n int
discount int
priceMap map[int]int
customerCount int
}
func Constructor(n int, discount int, products []int, prices []int) Cashier {
priceMap := make(map[int]int)
for i, prod := range products {
priceMap[prod] = prices[i]
}
return Cashier{
n: n,
discount: discount,
priceMap: priceMap,
customerCount: 0,
}
}
func (this *Cashier) GetBill(product []int, amount []int) float64 {
this.customerCount++
subtotal := 0
for i, prodID := range product {
subtotal += this.priceMap[prodID] * amount[i]
}
if this.customerCount%this.n == 0 {
return float64(subtotal) * float64(100-this.discount) / 100.0
}
return float64(subtotal)
}
The Go implementation mirrors the Python solution. A map provides constant-time lookups for product prices. Care is taken to cast integers to float64 when applying the discount to ensure proper floating-point division. The customer counter and discount logic remain identical.
Worked Examples
Consider the first example: Cashier(3, 50, [1,2,3,4,5,6,7], [100,200,300,400,300,200,100]).
| Customer | Products | Amounts | Subtotal | Discount Applied | Final Bill |
|---|---|---|---|---|---|
| 1 | [1,2] | [1,2] | 1_100 + 2_200 = 500 | No | 500.0 |
| 2 | [3,7] | [10,10] | 10_300 + 10_100 = 4000 | No | 4000.0 |
| 3 | [1,2,3,4,5,6,7] | [1,1,1,1,1,1,1] | 1600 | Yes | 1600 * 0.5 = 800.0 |
| 4 | [4] | [10] | 10*400 = 4000 | No | 4000.0 |
| 5 | [7,3] | [10,10] | 10_100 + 10_300 = 4000 | No | 4000.0 |
| 6 | [7,5,3,1,6,4,2] | [10,10,10,9,9,9,7] | 14700 | Yes | 14700 * 0.5 = 7350.0 |
| 7 | [2,3,5] | [5,3,2] | 1000 + 900 + 600 = 2500 | No | 2500.0 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m) | m = number of products in the current bill; each price is looked up in O(1) |
| Space | O(p) | p = number of products in the catalog; we store a hash map of prices |
Using a hash map reduces repeated searches and ensures that each getBill call runs in linear time relative to the number of products in that bill.
Test Cases
# Provided examples
cashier = Cashier(3,50,[1,2,3,4,5,6,7],[100,200,300,400,300,200,100])
assert abs(cashier.getBill([1,2],[1,2]) - 500.0) < 1e-5
assert abs(cashier.getBill([3,7],[10,10]) - 4000.0) < 1e-5
assert abs(cashier.getBill([1,2,3,4,5,6,7],[1,1,1,1,1,1,1]) - 800.0) < 1e-5
assert abs(cashier.getBill([4],[10]) - 4000.0) < 1e-5
assert abs(cashier.getBill([7,3],[10,10]) - 4000.0) < 1e-5
assert abs(cashier.getBill([7,5,3,1,6,4,2],[10,10,10,9,9,9,7]) - 7350.0) < 1e-5
assert abs(cashier.getBill([2,3,5],[5,3,2]) - 2500.0) < 1e-5
# Edge cases
cashier = Cashier(1,100,[1],[100