Facebook Pixel

3687. Library Late Fee Calculator 🔒

EasyArraySimulation
LeetCode ↗

Problem Description

You are given an integer array daysLate, where each element daysLate[i] tells you how many days late the i-th book was returned to the library.

Each book incurs a penalty (late fee) based on how late it was returned:

  • If the book is exactly 1 day late (daysLate[i] == 1), the penalty is 1.
  • If the book is between 2 and 5 days late (inclusive), the penalty is 2 * daysLate[i].
  • If the book is more than 5 days late (daysLate[i] > 5), the penalty is 3 * daysLate[i].

Your task is to compute the penalty for every book in the array and return the total penalty for all books combined.

Example:

If daysLate = [1, 3, 6]:

  • The first book is 1 day late, so its penalty is 1.
  • The second book is 3 days late, so its penalty is 2 * 3 = 6.
  • The third book is 6 days late, so its penalty is 3 * 6 = 18.

The total penalty is 1 + 6 + 18 = 25.

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

How We Pick the Algorithm

Why Simulation / Basic DSA?

This problem maps to Simulation / Basic DSA through a short path in the full flowchart.

SimulationorstraightforwardyesMath orbittricks?noSimulation /Basic DSA

The problem directly computes penalties per book based on simple threshold rules (1 day, 2-5 days, >5 days) and sums them.

Open in Flowchart

Intuition

The problem directly tells us how to calculate the penalty for each book — there is no hidden trick or optimization needed. This is a classic simulation problem: we just follow the rules as stated.

The key observation is that each book's penalty depends only on its own lateness, not on any other book. This independence means we can:

  1. Process each element of daysLate one by one.
  2. Apply the correct penalty rule based on which range the value falls into.
  3. Add up all the individual penalties.

Since the penalty rules form three distinct cases, it's natural to express them as a piecewise function:

f(x) = 1 when x == 1, f(x) = 2x when 2 <= x <= 5, and f(x) = 3x when x > 5.

Once we have this helper function, the answer is simply the sum of f(x) over every x in the array. A single pass through the array is enough, giving us a straightforward linear-time solution.

Solution Approach

We use simulation to directly translate the penalty rules into code.

Step 1: Define the penalty function

We define a helper function f(x) that computes the late fee for a single book based on how many days late it is:

f(x) = 1 if x = 1

f(x) = 2x if 2 <= x <= 5

f(x) = 3x if x > 5

In the code, this is implemented with simple conditional checks:

def f(x: int) -> int:
    if x == 1:
        return 1
    if x > 5:
        return 3 * x
    return 2 * x

Note the ordering of the checks:

  • First, we check x == 1 and return 1.
  • Next, we check x > 5 and return 3 * x.
  • If neither condition holds, x must be in the range [2, 5], so we return 2 * x — no explicit range check is needed.

Step 2: Sum the penalties

Since each book's penalty is independent, we iterate through every element x in daysLate, apply f(x), and accumulate the results:

return sum(f(x) for x in daysLate)

This uses a generator expression with Python's built-in sum, which processes the array in a single pass without building any intermediate list.

Complexity Analysis

  • Time complexity: O(n), where n is the length of daysLate. Each element is visited exactly once, and computing f(x) takes constant time.
  • Space complexity: O(1). Only a constant amount of extra space is used, since the generator expression does not materialize a new list.

Example Walkthrough

Let's trace the solution with daysLate = [4, 1, 7, 2].

We process each element one by one, applying the penalty function f(x), and keep a running total:

Step 1: x = 4

  • Is x == 1? No.
  • Is x > 5? No.
  • Neither condition holds, so x falls in the range [2, 5] → penalty = 2 * 4 = 8.
  • Running total: 8.

Step 2: x = 1

  • Is x == 1? Yes → penalty = 1.
  • Running total: 8 + 1 = 9.

Step 3: x = 7

  • Is x == 1? No.
  • Is x > 5? Yes → penalty = 3 * 7 = 21.
  • Running total: 9 + 21 = 30.

Step 4: x = 2

  • Is x == 1? No.
  • Is x > 5? No.
  • x is in [2, 5] → penalty = 2 * 2 = 4.
  • Running total: 30 + 4 = 34.

Result: After a single pass through the array, the accumulated sum is 34, which is the total penalty returned by sum(f(x) for x in daysLate).

IndexdaysLate[i]Rule AppliedPenaltyRunning Total
042 <= x <= 5 → 2x88
11x == 1 → 119
27x > 5 → 3x2130
322 <= x <= 5 → 2x434

This walkthrough highlights the two core ideas of the approach: each book's penalty is computed independently through the piecewise function, and the final answer is just the accumulation of those values in one linear scan.

Solution Implementation

1class Solution:
2    def lateFee(self, daysLate: List[int]) -> int:
3        # Calculate the fee for a single book based on how many days it is late
4        def calculate_fee(days: int) -> int:
5            # Exactly 1 day late: flat fee of 1
6            if days == 1:
7                return 1
8            # More than 5 days late: fee is 3 per day
9            if days > 5:
10                return 3 * days
11            # Between 2 and 5 days late (inclusive): fee is 2 per day
12            return 2 * days
13
14        # Sum up the fees for all books
15        return sum(calculate_fee(days) for days in daysLate)
16```
17
18**Explanation of the changes:**
19
201. **Naming**: The helper function `f` was renamed to `calculate_fee`, and its parameter `x` was renamed to `days` — both follow Python's `snake_case` convention and clearly convey intent. The method name `lateFee` and parameter `daysLate` were kept unchanged as required (they match LeetCode's signature).
21
222. **Comments**: Each branch of the fee logic is documented, making the tiered pricing rules (1 day → flat 1, 2–5 days → 2/day, 6+ days → 3/day) immediately clear to readers.
23
243. **Logic**: The algorithm itself is already optimal — a single O(n) pass with O(1) extra space — so no structural changes were needed.
25
26**Alternative approach** — if you prefer a more compact, expression-based style, the same logic can be written with a conditional expression:
27
28```python3
29class Solution:
30    def lateFee(self, daysLate: List[int]) -> int:
31        # 1 day late costs 1; 2-5 days cost 2 per day; 6+ days cost 3 per day
32        return sum(1 if d == 1 else 2 * d if d <= 5 else 3 * d for d in daysLate)
33
1class Solution {
2    /**
3     * Calculates the total late fee for all overdue items.
4     *
5     * Fee rules per item:
6     * - Exactly 1 day late:  fee is 1
7     * - 2 to 5 days late:    fee is 2 * daysLate
8     * - More than 5 days:    fee is 3 * daysLate
9     *
10     * @param daysLate array where each element is the number of days an item is late
11     * @return the sum of late fees for all items
12     */
13    public int lateFee(int[] daysLate) {
14        int totalFee = 0;
15
16        // Accumulate the fee for each overdue item
17        for (int days : daysLate) {
18            if (days == 1) {
19                // Late by exactly one day: flat fee of 1
20                totalFee += 1;
21            } else if (days > 5) {
22                // Late by more than five days: triple rate
23                totalFee += 3 * days;
24            } else {
25                // Late by 2 to 5 days: double rate
26                totalFee += 2 * days;
27            }
28        }
29
30        return totalFee;
31    }
32}
33```
34
35Key changes made:
36
371. **Removed the `IntUnaryOperator` lambda** — replaced it with a straightforward `if-else` chain inside the loop. This avoids the functional-interface overhead and keeps the logic inline, which is simpler and more idiomatic for such a small computation.
382. **Standardized naming** — `ans` became `totalFee` (describes what it holds), and the loop variable `x` became `days` (describes what it represents). The method name `lateFee` is preserved.
393. **Added comments** — a Javadoc block documents the fee rules and parameters, and inline comments explain each branch.
40
41An alternative perspective: if you prefer keeping the functional style, you could use a stream-based one-liner instead:
42
43```java
44class Solution {
45    public int lateFee(int[] daysLate) {
46        // Map each overdue duration to its fee, then sum them all
47        return java.util.Arrays.stream(daysLate)
48                .map(days -> days == 1 ? 1 : days > 5 ? 3 * days : 2 * days)
49                .sum();
50    }
51}
52
1class Solution {
2public:
3    int lateFee(vector<int>& daysLate) {
4        // Lambda function to calculate the fee for a single rental
5        // based on the number of days it is late.
6        auto calculateFee = [](int days) {
7            if (days == 1) {
8                // Late by exactly 1 day: flat fee of 1
9                return 1;
10            } else if (days > 5) {
11                // Late by more than 5 days: fee is 3 per day
12                return 3 * days;
13            } else {
14                // Late by 2 to 5 days: fee is 2 per day
15                return 2 * days;
16            }
17        };
18
19        int totalFee = 0;
20        // Accumulate the fee for each late rental
21        for (int days : daysLate) {
22            totalFee += calculateFee(days);
23        }
24        return totalFee;
25    }
26};
27```
28
29**Explanation of the changes:**
30
311. **Standardized naming**:
32   - `f` → `calculateFee`: describes the lambda's purpose clearly.
33   - `x` → `days`: conveys that the variable represents days late.
34   - `ans` → `totalFee`: indicates it holds the accumulated result.
35   - The method name `lateFee` remains unchanged as required.
36
372. **Comments added**: Each branch of the fee logic and the accumulation loop is documented, making the tiered pricing structure (1 day / 2–5 days / over 5 days) immediately clear to readers.
38
393. **Logic preserved**: The algorithm is identical — an O(n) single pass over `daysLate` with O(1) extra space.
40
41**Alternative perspective**: If you prefer a more functional style, the loop could be replaced with `std::accumulate`:
42
43```cpp
44int totalFee = accumulate(daysLate.begin(), daysLate.end(), 0,
45    [&](int sum, int days) { return sum + calculateFee(days); });
46
1/**
2 * Calculates the late fee for a single item based on the number of days it is late.
3 *
4 * Fee rules:
5 * - Exactly 1 day late: fee is 1.
6 * - More than 5 days late: fee is 3 per day.
7 * - Otherwise (2 to 5 days late): fee is 2 per day.
8 *
9 * @param days - The number of days an item is late.
10 * @returns The late fee for that item.
11 */
12const calculateFee = (days: number): number => {
13    if (days === 1) {
14        // Only 1 day late: flat fee of 1
15        return 1;
16    } else if (days > 5) {
17        // More than 5 days late: 3 per day
18        return 3 * days;
19    }
20    // Between 2 and 5 days late: 2 per day
21    return 2 * days;
22};
23
24/**
25 * Computes the total late fee for all items.
26 *
27 * @param daysLate - An array where each element represents the number of days an item is late.
28 * @returns The sum of late fees for all items.
29 */
30function lateFee(daysLate: number[]): number {
31    // Accumulate the fee of each item into a total sum
32    return daysLate.reduce((totalFee, days) => totalFee + calculateFee(days), 0);
33}
34

Time and Space Complexity

  • Time Complexity: O(n), where n is the length of the array daysLate. The code iterates through each element of the array exactly once, and for each element, the helper function f(x) performs only constant-time operations (comparisons and arithmetic).

  • Space Complexity: O(1). The solution uses a generator expression inside sum, which processes elements one at a time without creating an intermediate list, so only a constant amount of extra space is required.

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

Common Pitfalls

Pitfall: Mishandling boundary values due to incorrect condition ordering or off-by-one comparisons

The fee rules form three tiers with two boundaries (x = 1 and x = 5), and it is surprisingly easy to break the logic at exactly those points. Two frequent mistakes:

1. Using the wrong comparison operator at the 5-day boundary

# WRONG: a book that is exactly 5 days late is charged 3 * 5 = 15
# instead of the correct 2 * 5 = 10
def calculate_fee(days: int) -> int:
    if days == 1:
        return 1
    if days >= 5:      # should be days > 5
        return 3 * days
    return 2 * days

The rule says "between 2 and 5 days late (inclusive)" pays 2 * days, so days == 5 must fall into the middle tier. Writing >= 5 silently overcharges every 5-day-late book, and simple test cases like [1, 3, 6] will still pass — the bug only surfaces when the input contains exactly 5.

2. Reordering chained conditions in the compact ternary version

# WRONG: the d <= 5 branch swallows d == 1, so a 1-day-late book
# is charged 2 * 1 = 2 instead of 1
return sum(2 * d if d <= 5 else 1 if d == 1 else 3 * d for d in daysLate)

In a chained conditional expression, conditions are evaluated left to right, and the first match wins. Because d == 1 also satisfies d <= 5, the more specific check must come first; otherwise the d == 1 branch becomes unreachable dead code.

Solution

  • Order checks from most specific to most general: handle days == 1 first, then days > 5, and let the final branch cover the remaining [2, 5] range. This way no explicit range check is needed and no branch can shadow another.
  • Use a strict comparison (days > 5) for the top tier so that days == 5 correctly falls into the 2 * days tier.
  • Verify with boundary-focused test cases rather than only the sample input:
assert Solution().lateFee([1]) == 1        # boundary: exactly 1 day
assert Solution().lateFee([2]) == 4        # boundary: start of middle tier
assert Solution().lateFee([5]) == 10       # boundary: end of middle tier (NOT 15)
assert Solution().lateFee([6]) == 18       # boundary: start of top tier
assert Solution().lateFee([1, 3, 6]) == 25 # sample case

If your implementation passes all five assertions — especially the [5] case — the tier boundaries are handled correctly.

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

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

Load More