Talentd
FleetCode
Make and follow sheets.Sign in

Maximum Number of Groups Getting Fresh Donuts solution and explanation

HardArrayBit ManipulationBitmaskDynamic Programming7 min read41.7% acceptanceUpdated 9 Apr 2026
花花酱 LeetCode 1815. Maximum Number of Groups Getting Fresh Donuts - 刷题找工作 EP391Hua Hua · 21:58 · 2K viewsWatch on YouTube
6 more videos on this problem
  1. 花花酱 LeetCode 1815. Maximum Number of Groups Getting Fresh Donuts - 刷题找工作 EP391Hua Hua · 21:58 · 2K views
  2. LeetCode 1815. Maximum Number of Groups Getting Fresh DonutsHappy Coding · 23:20 · 1K views
  3. 1815. Maximum Number of Groups Getting Fresh Donuts (Leetcode Hard)Programming Live with Larry · 53:36 · 490 views
  4. 【每日一题】1815. Maximum Number of Groups Getting Fresh Donuts, 4/6/2021Huifeng Guan · 33:23 · 471 views
  5. LeetCode 1815. Maximum Number of Groups Getting Fresh Donuts | Biweekly Contest 49宰相小甘罗 · 9:06 · 338 views
  6. Leetcode 1815. Maximum Number of Groups Getting Fresh DonutsFearless Learner · 5:33 · 243 views
  7. LeetCode 1815. Maximum Number of Groups Getting Fresh Donuts 中文 | Biweekly Contest 49宰相小甘罗 · 7:43 · 212 views

The problem

There is a donuts shop that bakes donuts in batches of batchSize. They have a rule where they must serve all of the donuts of a batch before serving any donuts of the next batch. You are given an integer batchSize and an integer array groups, where groups[i] denotes that there is a group of groups[i] customers that will visit the shop. Each customer will get exactly one donut.

When a group visits the shop, all customers of the group must be served before serving any of the following groups. A group will be happy if they all get fresh donuts. That is, the first customer of the group does not receive a donut that was left over from the previous group.

You can freely rearrange the ordering of the groups. Return the maximum possible number of happy groups after rearranging the groups.

Example 1:

Input: batchSize = 3, groups = [1,2,3,4,5,6]
Output: 4
Explanation: You can arrange the groups as [6,2,4,5,1,3]. Then the 1st, 2nd, 4th, and 6th groups will be happy.

Example 2:

Input: batchSize = 4, groups = [1,3,2,5,2,2,1,6]
Output: 4

Constraints:

  • 1 <= batchSize <= 9
  • 1 <= groups.length <= 30
  • 1 <= groups[i] <= 109

Approach overview

Problem Overview: You run a donut shop where donuts are baked in fixed batch sizes. Each arriving group wants fresh donuts, meaning the first donut they receive must come from a new batch. The task is to reorder groups so the maximum number of them get fresh donuts.

The key observation: only the remainder groupSize % batchSize affects whether a group starts a fresh batch. This transforms the problem from large numbers into a frequency counting and state optimization problem.

Approach 1: Greedy Approach with Modulo Counting (O(n + batchSize²) time, O(batchSize) space)

Start by computing the remainder of every group size with batchSize. Groups with remainder 0 always start a fresh batch, so they directly increase the answer. For the rest, count frequencies of remainders using an array. Pair complementary remainders such that r + (batchSize - r) = batchSize. Each pair forms a complete batch and guarantees another fresh group.

For example, remainder 1 pairs with batchSize - 1. Greedily match these counts and reduce the remaining groups. This step eliminates obvious combinations quickly and significantly shrinks the search space. The greedy preprocessing works well because complementary pairs always produce full batches without affecting future decisions.

Approach 2: Dynamic Programming with Memoization (O(batchSize * states) time, exponential states pruned, O(states) space)

After greedy pairing, some remainder groups still remain. Their order determines how leftovers accumulate. Model this as a state search using dynamic programming with memoization. The state tracks the current leftover donuts and the remaining count of each remainder.

Represent the remainder counts compactly using bit manipulation or a bitmask-style encoded integer. Each recursive step chooses a remainder group, updates the leftover modulo batchSize, and continues. If the leftover becomes zero before serving a group, that group gets fresh donuts and increases the score.

Memoization caches results for previously seen states so identical remainder configurations are not recomputed. The search space is large in theory but small in practice because batchSize ≤ 9 and counts are limited. This DP formulation explores all valid permutations while pruning redundant states.

Recommended for interviews: Start by explaining the greedy remainder pairing since it demonstrates understanding of modular arithmetic and batching behavior. Then transition to the memoized DP that encodes remainder frequencies as state. Interviewers typically expect the DP optimization because brute force permutations are infeasible, and the memoized state search shows strong algorithmic reasoning.

Approach 1: Greedy Approach with Modulo Counting

This approach utilizes the property of modulo arithmetic to rearrange the groups such that the total number of leftover donuts between batch servings is minimized. By counting how many groups have a particular remainder when divided by batchSize, we can decide the optimal order to serve the groups for maximizing the number of happy groups.

def maxHappyGroups(batchSize, groups):
    count = [0] * batchSize
    for group in groups:
        count[group % batchSize] += 1

    result = count[0]
    for i in range(1, (batchSize + 1) // 2):
        if i != batchSize - i:
            result += min(count[i], count[batchSize - i])
        else:
            result += count[i] // 2
    return result

print(maxHappyGroups(3, [1, 2, 3, 4, 5, 6])) # Output: 4
import java.util.*;

public class Main {
    public static int maxHappyGroups(int batchSize, int[] groups) {
        int[] count = new int[batchSize];
        for (int group : groups) {
            count[group % batchSize]++;
        }

        int result = count[0];
        for (int i = 1; i <= batchSize / 2; i++) {
            if (i != batchSize - i) {
                result += Math.min(count[i], count[batchSize - i]);
            } else {
                result += count[i] / 2;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] groups = {1, 2, 3, 4, 5, 6};
        int batchSize = 3;
        System.out.println(maxHappyGroups(batchSize, groups)); // Output: 4
    }
}
#include <vector>
#include <unordered_map>
using namespace std;

int maxHappyGroups(int batchSize, const vector<int>& groups) {
    vector<int> count(batchSize, 0);
    for (int group : groups) {
        count[group % batchSize]++;
    }

    int result = count[0];
    for (int i = 1; i <= batchSize / 2; ++i) {
        if (i != batchSize - i) {
            result += min(count[i], count[batchSize - i]);
        } else {
            result += count[i] / 2;
        }
    }
    return result;
}

int main() {
    vector<int> groups = {1, 2, 3, 4, 5, 6};
    int batchSize = 3;
    int result = maxHappyGroups(batchSize, groups);
    printf("%d\n", result);
    return 0;
}
function maxHappyGroups(batchSize, groups) {
    let count = Array(batchSize).fill(0);
    for (let group of groups) {
        count[group % batchSize]++;
    }

    let result = count[0];
    for (let i = 1; i <= batchSize / 2; i++) {
        if (i != batchSize - i) {
            result += Math.min(count[i], count[batchSize - i]);
        } else {
            result += Math.floor(count[i] / 2);
        }
    }
    return result;
}

console.log(maxHappyGroups(3, [1, 2, 3, 4, 5, 6])); // Output: 4
#include <stdio.h>

int maxHappyGroups(int batchSize, int* groups, int groupsSize) {
    int count[10] = {0};
    for (int i = 0; i < groupsSize; i++) {
        count[groups[i] % batchSize]++;
    }

    int result = count[0];
    for (int i = 1; i <= batchSize / 2; i++) {
        if (i != batchSize - i) {
            int pairedGroups = count[i] < count[batchSize - i] ? count[i] : count[batchSize - i];
            result += pairedGroups;
        } else {
            result += count[i] / 2;
        }
    }
    return result;
}

int main() {
    int groups[] = {1, 2, 3, 4, 5, 6};
    int batchSize = 3;
    int result = maxHappyGroups(batchSize, groups, sizeof(groups)/sizeof(groups[0]));
    printf("%d\n", result);
    return 0;
}
using System;

public class Program {
    public static int MaxHappyGroups(int batchSize, int[] groups) {
        int[] count = new int[batchSize];
        foreach (int group in groups) {
            count[group % batchSize]++;
        }

        int result = count[0];
        for (int i = 1; i <= batchSize / 2; i++) {
            if (i != batchSize - i) {
                result += Math.Min(count[i], count[batchSize - i]);
            } else {
                result += count[i] / 2;
            }
        }
        return result;
    }

    public static void Main(string[] args) {
        int[] groups = {1, 2, 3, 4, 5, 6};
        int batchSize = 3;
        Console.WriteLine(MaxHappyGroups(batchSize, groups)); // Output: 4
    }
}

The Python solution defines a modulo frequency counter to determine how many full groups of a batchSize can be formed by combining groups with complementary remainders.

Complexity

Time Complexity: O(n), where n is the total number of groups.
Space Complexity: O(batchSize), the space needed to maintain the list of remainders.

Try this approach in the editor

Approach 2: Dynamic Programming with Memoization

This approach uses a recursive function combined with memoization to explore all possible arrangements of groups. We use dynamic programming to store results of previously computed states to avoid redundant calculations.

def recurse(count, mask, batchSize, dp):
    if dp[mask] != -1:
        return dp[mask]
    max_happy = 0
    for i in range(batchSize):
        if count[i] > 0:
            count[i] -= 1
            next_mask = mask | (1 << i)
            max_happy = max(max_happy, recurse(count, next_mask, batchSize, dp))
            count[i] += 1
    dp[mask] = max_happy
    return max_happy


def maxHappyGroups(batchSize, groups):
    count = [0] * batchSize
    for group in groups:
        count[group % batchSize] += 1

    dp = [-1] * (1 << (batchSize - 1))
    return recurse(count, 0, batchSize, dp)


print(maxHappyGroups(4, [1, 3, 2, 5, 2, 2, 1, 6])) # Output: 4
import java.util.*;

public class Main {

    public static int recurse(int[] count, int mask, int batchSize, int[][] dp) {
        if (dp[mask][batchSize] != -1) {
            return dp[mask][batchSize];
        }
        int answer = 0;
        for (int i = 0; i < batchSize; ++i) {
            if (count[i] > 0) {
                --count[i];
                int nextMask = mask | (1 << i);
                answer = Math.max(answer, recurse(count, nextMask, batchSize, dp));
                count[i]++;
            }
        }
        dp[mask][batchSize] = answer;
        return answer;
    }

    public static int maxHappyGroups(int batchSize, int[] groups) {
        int count[] = new int[batchSize];
        for (int group : groups) {
            count[group % batchSize]++;
        }

        int[][] dp = new int[1 << (batchSize - 1)][batchSize];
        for (int[] d : dp) Arrays.fill(d, -1);
        return recurse(count, 0, batchSize, dp);
    }

    public static void main(String[] args) {
        int[] groups = {1, 3, 2, 5, 2, 2, 1, 6};
        int batchSize = 4;
        System.out.println(maxHappyGroups(batchSize, groups)); // Output: 4
    }
}
#include <vector>
#include <unordered_map>
using namespace std;

int recurse(vector<int>& count, int mask, int batchSize, vector<vector<int>>& dp) {
    if (dp[mask] != -1) {
        return dp[mask];
    }
    int answer = 0;
    for (int i = 0; i < batchSize; ++i) {
        if (count[i] > 0) {
            --count[i];
            int next_mask = mask | (1 << i);
            answer = max(answer, recurse(count, next_mask, batchSize, dp));
            ++count[i];
        }
    }
    dp[mask] = answer;
    return answer;
}

int maxHappyGroups(int batchSize, const vector<int>& groups) {
    vector<int> count(batchSize, 0);
    for (int group : groups) {
        count[group % batchSize]++;
    }

    vector<vector<int>> dp(1 << (batchSize - 1), vector<int>(-1));
    return recurse(count, 0, batchSize, dp);
}

int main() {
    vector<int> groups = {1, 3, 2, 5, 2, 2, 1, 6};
    int batchSize = 4;
    int result = maxHappyGroups(batchSize, groups);
    printf("%d\n", result);
    return 0;
}
function recurse(count, mask, batchSize, dp) {
    if (dp[mask] !== -1) return dp[mask];
    let maxHappy = 0;
    for (let i = 0; i < batchSize; i++) {
        if (count[i] > 0) {
            count[i]--;
            let nextMask = mask | (1 << i);
            maxHappy = Math.max(maxHappy, recurse(count, nextMask, batchSize, dp));
            count[i]++;
        }
    }
    dp[mask] = maxHappy;
    return maxHappy;
}

function maxHappyGroups(batchSize, groups) {
    let count = Array(batchSize).fill(0);
    for (let group of groups) {
        count[group % batchSize]++;
    }

    let dp = new Array(1 << (batchSize - 1)).fill(-1);
    return recurse(count, 0, batchSize, dp);
}

console.log(maxHappyGroups(4, [1, 3, 2, 5, 2, 2, 1, 6])); // Output: 4
#include <stdio.h>
#include <string.h>

#define MAX_BATCH 9
#define MAX_GROUPS 30
#define INVALID -1

int dp[1 << MAX_BATCH][MAX_BATCH];

int maxHappyGroupsUtil(int batchSize, int* count, int mask, int remainder) {
    if (dp[mask][remainder] != INVALID) {
        return dp[mask][remainder];
    }
    int maxHappy = 0;
    for (int i = 0; i < batchSize; ++i) {
        if (((mask >> i) & 1) && count[i] > 0) {
            count[i]--;
            maxHappy = count[i] + ((remainder + i) % batchSize == 0);
            maxHappy = maxHappyGroupsUtil(batchSize, count, mask | (1 << i), (remainder + i) % batchSize);
            count[i]++;
        }
    }
    dp[mask][remainder] = maxHappy;
    return maxHappy;
}

int maxHappyGroups(int batchSize, int* groups, int groupsSize) {
    int count[MAX_BATCH] = {0};
    for (int i = 0; i < groupsSize; ++i) {
        count[groups[i] % batchSize]++;
    }
    memset(dp, INVALID, sizeof(dp));
    return maxHappyGroupsUtil(batchSize, count, 1, 0);
}

int main() {
    int groups[] = {1, 3, 2, 5, 2, 2, 1, 6};
    int batchSize = 4;
    int result = maxHappyGroups(batchSize, groups, sizeof(groups)/sizeof(groups[0]));
    printf("%d\n", result);
    return 0;
}
using System;

public class Program {

    static int Recurse(int[] count, int mask, int batchSize, int[][] dp) {
        if (dp[mask][batchSize] != -1) {
            return dp[mask][batchSize];
        }
        int maxHappy = 0;
        for (int i = 0; i < batchSize; ++i) {
            if (count[i] > 0) {
                count[i]--;
                int nextMask = mask | (1 << i);
                maxHappy = Math.Max(maxHappy, Recurse(count, nextMask, batchSize, dp));
                count[i]++;
            }
        }
        dp[mask][batchSize] = maxHappy;
        return maxHappy;
    }

    public static int MaxHappyGroups(int batchSize, int[] groups) {
        int[] count = new int[batchSize];
        foreach (int group in groups) {
            count[group % batchSize]++;
        }

        int[][] dp = new int[1 << (batchSize - 1)][];
        for (int i = 0; i < dp.Length; i++) {
            dp[i] = new int[batchSize];
            Array.Fill(dp[i], -1);
        }
        return Recurse(count, 0, batchSize, dp);
    }

    public static void Main(string[] args) {
        int[] groups = {1, 3, 2, 5, 2, 2, 1, 6};
        int batchSize = 4;
        Console.WriteLine(MaxHappyGroups(batchSize, groups)); // Output: 4
    }
}

The Python version utilizes a recursive function with memoization for efficient computation, ensuring all different permutations are considered by embedding state tracking within a bitmask.

Complexity

Time Complexity: O(2^batchSize * batchSize), needed due to the exploration of subset permutations.
Space Complexity: O(2^batchSize * batchSize), a requirement for the memoization table.

Try this approach in the editor

Approach 3: Greedy + State Compression + Memorized Search

The problem actually asks us to find an arrangement order that maximizes the number of groups whose prefix sum (referring to "number of people" here) modulo batchSize equals 0. Therefore, we can divide all customers into two categories:

  • Customers whose number is a multiple of batchSize. These customers will not affect the donuts of the next group of customers. We can greedily arrange these groups of customers first, so these groups of customers will be happy. The "initial answer" is the number of these groups.
  • Customers whose number is not a multiple of batchSize. The arrangement order of these customers will affect the donuts of the next group of customers. We can take the modulo batchSize for the number of people v in each group here, and the remainders form a set. The range of element values in the set is [1,2...,batchSize-1]. The maximum length of the groups array is 30, so the maximum number of each remainder does not exceed 30. We can use 5 binary bits to represent the quantity of a remainder, and the maximum batchSize is 9, so the total number of binary bits required to represent these remainders and their quantities is 5times (9-1)=40. We can use a 64-bit integer state to represent it.

Next, we design a function dfs(state, mod), which represents the number of groups that can be happy when the arrangement state is state and the current prefix remainder is mod. Then our "initial answer" plus dfs(state, 0) is the final answer.

The implementation logic of the function dfs(state, mod) is as follows:

We enumerate each remainder i from 1 to batchSize-1. If the quantity of the remainder i is not 0, we can subtract 1 from the quantity of the remainder i, add i to the current prefix remainder and take modulo batchSize, then recursively call the function dfs to find the optimal solution of the sub-state, and take the maximum value. Finally, check whether mod is 0. If it is 0, we return after adding 1 to the maximum value, otherwise we directly return the maximum value.

During the process, we can use memorized search to avoid repeated calculation of states.

The time complexity does not exceed O(10^7), and the space complexity does not exceed O(10^6).

func maxHappyGroups(batchSize int, groups []int) (ans int) {
	state := 0
	for _, v := range groups {
		i := v % batchSize
		if i == 0 {
			ans++
		} else {
			state += 1 << (i * 5)
		}
	}
	f := map[int]int{}
	var dfs func(int, int) int
	dfs = func(state, mod int) int {
		if v, ok := f[state]; ok {
			return v
		}
		res := 0
		x := 0
		if mod == 0 {
			x = 1
		}
		for i := 1; i < batchSize; i++ {
			if state>>(i*5)&31 != 0 {
				t := dfs(state-1<<(i*5), (mod+i)%batchSize)
				res = max(res, t+x)
			}
		}
		f[state] = res
		return res
	}
	ans += dfs(state, 0)
	return
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Greedy Approach with Modulo Counting

Time Complexity: O(n), where n is the total number of groups.
Space Complexity: O(batchSize), the space needed to maintain the list of remainders.

Dynamic Programming with Memoization

Time Complexity: O(2^batchSize * batchSize), needed due to the exploration of subset permutations.
Space Complexity: O(2^batchSize * batchSize), a requirement for the memoization table.

Greedy + State Compression + Memorized Search–

Complexity analysis

ApproachTimeSpaceWhen to Use
Greedy with Modulo CountingO(n + batchSize²)O(batchSize)Initial optimization to pair complementary remainders and reduce the search space
Dynamic Programming with MemoizationO(batchSize * states)O(states)General case where leftover remainder groups require exploring optimal ordering

Frequently asked questions

Is Maximum Number of Groups Getting Fresh Donuts easy or hard?

Maximum Number of Groups Getting Fresh Donuts is classified as a Hard problem on LeetCode. The difficulty comes from recognizing that only remainders matter, applying greedy reductions, and designing a memoized dynamic programming state that encodes remaining group counts efficiently.

Maximum Number of Groups Getting Fresh Donuts Python/Java solution

Implementations typically compute remainder frequencies, apply greedy pairing, then run a recursive DP with memoization. Python solutions often use dictionaries or functools.lru_cache for caching states, while Java implementations use HashMap for memoization. Both follow the same remainder-count state transition logic.

How to solve Maximum Number of Groups Getting Fresh Donuts in O(n)?

A pure O(n) solution is not possible because the ordering of remaining groups affects the outcome. However, you can reduce the problem significantly in O(n) time by counting remainders and greedily pairing complementary values. After that reduction, a memoized DP searches the remaining configurations efficiently.

What is the best approach for Maximum Number of Groups Getting Fresh Donuts?

The optimal strategy combines greedy remainder pairing with dynamic programming and memoization. First count group remainders modulo batchSize and greedily pair complementary remainders that sum to batchSize. For the remaining groups, use DP with memoization to explore orders of serving groups while tracking leftover donuts. This avoids brute force permutations and keeps the state space manageable.

Is Maximum Number of Groups Getting Fresh Donuts asked at Google/Amazon/Meta?

This problem appears in interview preparation sets for companies such as Google and other large tech firms because it tests modular arithmetic, dynamic programming, and state compression. The combination of greedy preprocessing and DP with memoization reflects the type of optimization problems commonly asked in senior interviews.

What data structure is used in Maximum Number of Groups Getting Fresh Donuts?

The solution primarily uses an array to store counts of group remainders modulo batchSize. The optimized version also uses memoization with a hash map or dictionary to cache DP states. Bitmask-style state encoding or bit manipulation helps compress remainder counts into a compact representation.

What is the time complexity of Maximum Number of Groups Getting Fresh Donuts?

The greedy preprocessing runs in O(n + batchSize²) time. The dynamic programming stage explores encoded remainder states with memoization, giving roughly O(batchSize * states) time where states represent unique remainder frequency combinations. Space complexity is O(states) for the memoization cache.

Comments

Now write it yourselfClose this page and solve Maximum Number of Groups Getting Fresh Donuts in the editor, without looking.
Practise Maximum Number of Groups Getting Fresh Donuts