AlgoMaster Logo

Minimum Number of Work Sessions to Finish the Tasks

mediumFrequency8 min readUpdated June 23, 2026

Understanding the Problem

We need to partition tasks into groups (sessions), where the sum of task times in each group is at most sessionTime, and we want to minimize the number of groups.

This is the bin packing problem: fit items into bins of fixed capacity, minimize the number of bins used. Bin packing is NP-hard in general, so there is no known polynomial algorithm. What makes this version tractable is that n is at most 14, small enough for exponential algorithms to run quickly but too large for brute-force factorial enumeration of all orderings.

With 14 items there are 2^14 = 16,384 possible subsets, a count small enough to store in an array and iterate over. That points to a bitmask approach: represent the set of completed tasks as a bitmask, and for each state track how many sessions have been used and how much time remains in the current session.

Key Constraints

  • n <= 14: 2^14 = 16,384 subsets of tasks, which is small enough to index a DP array by the subset of completed tasks.
  • tasks[i] <= 10 and sessionTime <= 15: the remaining time in a session is bounded by 15, so it can be stored compactly as part of the state.
  • max(tasks[i]) <= sessionTime: every individual task fits in one session, so the answer always exists and is at most n.

Approach 1: Backtracking with Pruning

Intuition

Process tasks one at a time. For each task, either place it into an existing session that still has room, or open a new session for it. Exploring every such choice with backtracking covers all possible assignments of tasks to sessions.

We maintain a list of sessions, each holding its current total time. For each task we try adding it to each existing session where it fits, and we also try opening a new session. We track the minimum session count across all valid arrangements.

Two optimizations keep this from exploring the full search space. First, prune: once we have a solution using best sessions, any branch that already uses best or more sessions cannot improve the answer, so we stop. Second, sort tasks in descending order. Placing large tasks first fills sessions faster, which raises the session count earlier and lets the prune cut branches sooner.

Algorithm

  1. Sort tasks in descending order (larger tasks first reduces branching).
  2. Start with an empty list of sessions.
  3. For each task (in order), try two options:
    • Add it to an existing session that has enough remaining capacity.
    • Start a new session with this task.
  4. Prune: if the number of sessions reaches or exceeds the current best answer, stop exploring.
  5. When all tasks are placed, update the global minimum.
  6. Return the minimum number of sessions found.

Example Walkthrough

1Sorted descending: tasks = [3, 2, 1], sessionTime = 3. best = 3.
0
3
1
2
2
1
1/6

Code

Backtracking re-explores the same set of completed tasks many times, once for every order in which those tasks could have been placed. The next approach removes that repetition by keying a DP table on the set of completed tasks, represented as a bitmask.

Approach 2: Bitmask DP (Sessions + Remaining Time)

Intuition

With n <= 14, the set of completed tasks fits in a bitmask: if bit i is set, task i is done. There are 2^14 = 16,384 such states.

The set of completed tasks alone is not enough to decide the next move. We also need to know the current session's load, since that determines whether the next task fits in the current session or forces a new one. So the DP state is dp[mask] holding a pair: the number of sessions used and the time already used in the current session.

To compare two states with a single min() call, both values are packed into one integer: (sessions << 16) | used, where used is the time consumed in the current session. Sessions occupy the high bits and used time the low bits, so a smaller packed value means fewer sessions first, and among equal session counts, less time used. Less time used means more time remaining, the state we want to keep among ties. The remaining time at any point is sessionTime - used. The Python version stores the equivalent pair (sessions, -remaining) and relies on tuple comparison, which orders the same way.

Algorithm

  1. Initialize dp[0] to one session with zero time used (full capacity remaining), packed as (1 << 16) | 0.
  2. For each mask from 0 to 2^n - 1, decode sessions and used, with remaining = sessionTime - used. Try adding each unfinished task i (where bit i is 0):
    • If tasks[i] <= remaining, stay in the current session and set used to used + tasks[i].
    • Otherwise, start a new session: increment sessions and set used to tasks[i].
    • Update dp[mask | (1 << i)] with the smaller of its current packed value and the new one.
  3. The answer is dp[(1 << n) - 1] >> 16, the session count when all tasks are done.

Example Walkthrough

1Init: dp[000] = (sessions=1, remain=3). One empty session.
000
:
(1, 3)
1/6

Code

The O(n * 2^n) approach runs well within limits. A different DP formulation works at the level of whole sessions rather than one task at a time: precompute which subsets fit in a single session, then partition the full task set into the fewest such subsets.

Approach 3: Subset DP with Precomputed Valid Sessions

Intuition

This approach works at the session level instead of the task level. Each session is one valid subset of tasks (one whose total time is at most sessionTime), and the answer is the smallest number of such subsets that partition all the tasks.

Define dp[mask] as the minimum number of sessions to complete exactly the tasks in mask. To build dp[mask], choose one subset sub of mask to be the last session. If the tasks in sub fit in one session, the rest (mask ^ sub) is solved recursively: dp[mask] = min(dp[mask], dp[mask ^ sub] + 1). Trying every valid sub as the final session covers all partitions.

Enumerating every subset of a mask uses a standard bit manipulation loop: start from sub = mask, then repeatedly compute sub = (sub - 1) & mask until sub reaches 0. This visits each subset of mask exactly once. Summed over all masks, the total number of (mask, sub) pairs is 3^n, since each bit is independently in sub, in mask but not sub, or outside mask.

Algorithm

  1. Precompute the sum of tasks for every subset (bitmask). Mark which subsets have sum <= sessionTime (valid single-session groups).
  2. Initialize dp[0] = 0 (zero tasks need zero sessions).
  3. For each mask from 1 to 2^n - 1, enumerate all non-empty subsets sub of mask. If sub is valid, update: dp[mask] = min(dp[mask], dp[mask ^ sub] + 1).
  4. Return dp[(1 << n) - 1].

Example Walkthrough

1Init: dp[000] = 0 (zero tasks = zero sessions)
000
:
0
1/7

Code