Slowest reading pace that still finishes on time

Searching sorted data Mid-level Python 3.13+ Kotlin 2.4+ Reviewed By Best Latest Apps

Predict the output

What does this print?

Python 3.13
chapters = [3, 6, 7, 11]

def hours_at(pace: int) -> int:
    return sum(-(-c // pace) for c in chapters)

print([hours_at(p) for p in (3, 4, 6, 11)])

Options

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A [10, 8, 6, 4]

Right: faster paces need fewer hours, never more.

Hours fall as pace rises, so the first pace that fits the limit can be found by halving.

Why not the others?
  • B [9, 7, 5, 4]. Ceiling division rounds each chapter up separately.
  • C [10, 8, 6, 3]. At pace 11 every chapter still takes at least one hour.

30-second answer

Faster paces never take longer, so 'finishes in time' flips from False to True exactly once as p grows. Binary search p between 1 and the biggest chapter, testing each candidate in O(n). Total O(n log m) where m is the largest chapter, with O(1) extra space.

Practise saying it · 60 s. No recording, just a timer

Explain binary search on the answer.

Say your answer out loud, then check which of these points you covered:

  • Monotonic check
  • Search the value range
  • O(n log m)

Deep dive

This is binary search on the answer: the list being searched is the range of possible paces.

-(-a // b) is integer ceiling division in Python without floats.

The upper bound max(chapters) always works because each chapter then takes one hour.

Python solution Ran on Python 3.13.5 · mypy --strict
Python 3.13
def slowest_pace(chapters: list[int], hours: int) -> int:
    def hours_needed(pace: int) -> int:
        return sum(-(-pages // pace) for pages in chapters)

    lo, hi = 1, max(chapters)
    while lo < hi:
        mid = (lo + hi) // 2
        if hours_needed(mid) <= hours:
            hi = mid
        else:
            lo = mid + 1
    return lo

Kotlin Ran on Kotlin 2.4.21 · JDK 21.0.12.1
Kotlin 2.4
fun slowestPace(chapters: List<Int>, hours: Int): Int {
    fun hoursNeeded(pace: Int): Long =
        chapters.sumOf { ((it + pace - 1) / pace).toLong() }
    var lo = 1
    var hi = chapters.max()
    while (lo < hi) {
        val mid = (lo + hi) ushr 1
        if (hoursNeeded(mid) <= hours) hi = mid else lo = mid + 1
    }
    return lo
}

Common mistakes

  • Searching up to sum(chapters), which is correct but wastes steps.
  • Using float division and math.ceil, which can misround for huge values.

Senior take & follow-ups

Say the monotonic property out loud: 'if pace p works, any faster pace works too'.

Follow-up questions

Think of your answer first, then open the hint.

  1. How do you recognise 'binary search on the answer'?

    Show hint

    The question asks for a minimum or maximum value, and checking one candidate is easy and monotonic.

  2. What if hours could be fewer than chapters?

    Show hint

    Then no pace works; return -1 after checking the upper bound.

What each operation costs: Binary Search · Linear Search

From the author

DSA Master - Data & Algorithms

Free on iPhone and iPad: watch 50+ data structures and algorithms step through animations, run the code in the playground, and try the daily challenge.

Suggest a fix if something here is wrong or out of date.