AlgoMaster Logo

Minimum Number of Work Sessions to Finish the Tasks

Last Updated: April 2, 2026

medium
5 min read

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 looks a lot like bin packing: fit items into bins of fixed capacity, minimize the number of bins used. The key wrinkle is that n is at most 14, which is small enough for exponential approaches but too large for factorial ones to be efficient.

The constraint n <= 14 is a huge hint. With 14 items, we have 2^14 = 16,384 possible subsets. That's a very comfortable number for bitmask-based techniques. The core observation is that we can represent which tasks have been completed as a bitmask, and for each state, we care about how many sessions we've used and how much time remains in the current session.

Key Constraints:

  • n <= 14 -> This screams bitmask DP. We have at most 2^14 = 16,384 subsets of tasks.
  • tasks[i] <= 10 and sessionTime <= 15 -> The remaining time in a session is bounded by 15, which we can encode as part of the state.
  • max(tasks[i]) <= sessionTime -> Every single task fits in one session. So we never have an impossible case.

Approach 1: Backtracking with Pruning

Intuition

The most natural way to think about this: try placing each task into an existing session (if it fits) or start a new session. This is essentially the bin packing problem solved via backtracking.

We maintain a list of sessions, each with its current total time. For each task, we try to add it to each existing session where it fits, or we open a new session. We track the minimum number of sessions across all valid arrangements.

To make this practical, we need pruning. If we've already found a solution with k sessions, we can skip any branch that already uses k or more sessions. Sorting tasks in descending order also helps, since placing large tasks first reduces the branching factor early.

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

The backtracking approach explores a massive decision tree, repeatedly solving the same subproblem for different orderings. What if we could remember the result for each unique "which tasks are left" state using a bitmask?

Approach 2: Bitmask DP (Sessions + Remaining Time)

Intuition

With n <= 14, we can represent the set of completed tasks as a bitmask. If bit i is set, task i has been completed. There are only 2^14 = 16,384 possible states.

But which tasks are done isn't the full story. We also need to know how much time remains in the current session, because that determines whether the next task we pick fits in the current session or forces us to open a new one.

So our DP state is dp[mask], storing a pair: (number of sessions used, remaining time in the current session). We pack both values into a single integer where sessions occupy the high bits and remaining time occupies the low bits. This way, standard min() comparison prioritizes fewer sessions, which is our optimization target.

Algorithm

  1. Initialize dp[0] = (1, sessionTime) meaning: zero tasks done, we've used 1 session with full capacity remaining.
  2. For each mask from 0 to 2^n - 1, try adding each unfinished task i (where bit i is 0):
    • If tasks[i] <= remaining time in current session, stay in the current session.
    • Otherwise, start a new session with remaining time reset to sessionTime - tasks[i].
    • Update dp[mask | (1 << i)] with the better of its current value and the new state.
  3. Answer is dp[(1 << n) - 1] >> 16 (the number of sessions 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 is already fast, but there's an alternative that some interviewers prefer: precompute which subsets fit in a single session, then use subset enumeration DP to partition tasks into the fewest valid groups.

Approach 3: Subset DP with Precomputed Valid Sessions

Intuition

Instead of adding tasks one at a time, let's think at a higher level. First, figure out all subsets of tasks that can fit in a single session. Then, the problem becomes: partition the full set of tasks into the fewest number of valid subsets.

We define dp[mask] as the minimum number of sessions to complete exactly the tasks in mask. For every valid subset sub of mask (where the tasks in sub fit in one session), we have dp[mask] = min(dp[mask], dp[mask ^ sub] + 1).

The trick to enumerating all subsets of a mask efficiently is a classic bit manipulation technique: start from sub = mask, then repeatedly compute sub = (sub - 1) & mask until sub becomes 0. This enumerates all subsets without redundancy and runs in O(3^n) total across all masks.

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