LeetCode 2240 - Number of Ways to Buy Pens and Pencils
The problem asks us to determine the number of distinct ways to spend a given amount of money, total, on pens and pencils, each with fixed costs, cost1 for pens and cost2 for pencils.
Difficulty: 🟡 Medium
Topics: Math, Enumeration
Solution
Problem Understanding
The problem asks us to determine the number of distinct ways to spend a given amount of money, total, on pens and pencils, each with fixed costs, cost1 for pens and cost2 for pencils. You can buy any non-negative number of each item, including zero, as long as the total cost does not exceed your budget.
The inputs are three positive integers: total, cost1, and cost2. The output is a single integer representing the number of unique combinations of pens and pencils that can be purchased.
Key observations from the constraints are that total, cost1, and cost2 can go up to 1,000,000, so any solution that iterates over all possible combinations naively (e.g., double loops for pens and pencils) could be too slow. Important edge cases include scenarios where the total is smaller than the cost of both items, which should result in only one valid combination: buying zero pens and zero pencils.
The problem guarantees all inputs are positive integers, so there is no need to handle zero or negative costs.
Approaches
The brute-force approach iterates over every possible number of pens you could buy (from 0 to total // cost1) and for each, iterates over every possible number of pencils that can fit within the remaining budget. This approach is guaranteed to produce the correct answer because it explicitly enumerates all valid combinations. However, it is inefficient: if total is large and cost1 and cost2 are small, the number of iterations could be on the order of millions, which could be too slow for LeetCode constraints.
The optimal approach uses a mathematical observation. For each possible number of pens, p, the maximum number of pencils you can buy is (total - p * cost1) // cost2. This means we only need a single loop iterating over possible pen counts, and for each, we compute the pencil count in constant time rather than using a nested loop. This reduces the time complexity significantly while still counting all valid combinations.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(total / cost1 * total / cost2) | O(1) | Double loop over pens and pencils |
| Optimal | O(total / cost1) | O(1) | Single loop over pens, compute pencils directly |
Algorithm Walkthrough
- Initialize a variable
waysto 0, which will store the total number of valid purchase combinations. - Loop over every possible number of pens,
num_pens, from 0 to the maximum possible (total // cost1). This ensures we consider all pen quantities that do not exceed the budget. - For each
num_pens, calculate the remaining money after buying pens:remaining = total - num_pens * cost1. - Compute the maximum number of pencils that can be bought with the remaining money:
max_pencils = remaining // cost2. - Each combination of
num_penswith 0 tomax_pencilspencils is valid, so incrementwaysbymax_pencils + 1. - After finishing the loop, return
ways.
Why it works: The algorithm enumerates all valid pen counts, and for each pen count, it directly calculates the range of valid pencil counts. This guarantees that all valid combinations are counted exactly once. The use of integer division ensures that we never exceed the budget.
Python Solution
class Solution:
def waysToBuyPensPencils(self, total: int, cost1: int, cost2: int) -> int:
ways = 0
max_pens = total // cost1
for num_pens in range(max_pens + 1):
remaining = total - num_pens * cost1
max_pencils = remaining // cost2
ways += max_pencils + 1
return ways
The Python implementation follows the optimal approach. We first determine the maximum number of pens that can be bought. For each pen count, we calculate the remaining money and then determine how many pencils can fit in that remaining budget. Adding max_pencils + 1 accounts for all possible numbers of pencils, including zero.
Go Solution
func waysToBuyPensPencils(total int, cost1 int, cost2 int) int64 {
var ways int64 = 0
maxPens := total / cost1
for numPens := 0; numPens <= maxPens; numPens++ {
remaining := total - numPens*cost1
maxPencils := remaining / cost2
ways += int64(maxPencils + 1)
}
return ways
}
In Go, we use int64 for the result because the number of combinations can exceed int for large totals. The logic mirrors the Python solution, with explicit type conversions for safe arithmetic on large values.
Worked Examples
Example 1: total = 20, cost1 = 10, cost2 = 5
| num_pens | remaining | max_pencils | ways added | cumulative ways |
|---|---|---|---|---|
| 0 | 20 | 4 | 5 | 5 |
| 1 | 10 | 2 | 3 | 8 |
| 2 | 0 | 0 | 1 | 9 |
Result: 9
Example 2: total = 5, cost1 = 10, cost2 = 10
| num_pens | remaining | max_pencils | ways added | cumulative ways |
|---|---|---|---|---|
| 0 | 5 | 0 | 1 | 1 |
Result: 1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(total / cost1) | We iterate only over the possible pen counts, which is at most total // cost1 + 1 |
| Space | O(1) | We only use a few integer variables to store intermediate results |
Since we avoid nested loops, the algorithm is efficient even for total up to 10^6.
Test Cases
# Provided examples
assert Solution().waysToBuyPensPencils(20, 10, 5) == 9 # multiple options
assert Solution().waysToBuyPensPencils(5, 10, 10) == 1 # cannot buy any
# Boundary values
assert Solution().waysToBuyPensPencils(1, 1, 1) == 2 # total is equal to cost
assert Solution().waysToBuyPensPencils(1_000_000, 1, 1) == 500001000001 # large total, small costs
# Only one type affordable
assert Solution().waysToBuyPensPencils(10, 5, 20) == 3 # only pens affordable
assert Solution().waysToBuyPensPencils(10, 20, 5) == 3 # only pencils affordable
# Both prices are higher than total
assert Solution().waysToBuyPensPencils(3, 5, 5) == 1 # can buy nothing
| Test | Why |
|---|---|
| 20,10,5 | Normal multiple purchase scenario |
| 5,10,10 | Cannot buy anything scenario |
| 1,1,1 | Minimal input |
| 1_000_000,1,1 | Stress test with maximum total |
| 10,5,20 | Only one item affordable |
| 3,5,5 | Both items too expensive |
Edge Cases
- Cannot afford any item: When both
cost1andcost2are larger thantotal, only the zero purchase is valid. The implementation correctly adds 1 for this case. - Total divisible by one cost but not the other: This tests the correct integer division calculation to determine the number of pencils for each pen count. The algorithm calculates the maximum pencils using
//to handle integer division correctly. - Large total with small costs: The number of iterations can be very large. Using integer arithmetic ensures there is no floating-point precision issue and that memory usage remains constant. The Go version explicitly uses
int64to handle the large sum of combinations without overflow.