Hard

题目描述

有一个甜甜圈商店,每次烘焙 batchSize 个甜甜圈。他们有一个规则,必须在供应下一批甜甜圈之前,先把当前批次的甜甜圈全部供应完。

给你一个整数 batchSize 和一个整数数组 groups,其中 groups[i] 表示有一组 groups[i] 个顾客会来到商店。每个顾客都会得到恰好一个甜甜圈。

当一组顾客来到商店时,必须在为下一组顾客服务之前为该组的所有顾客服务。如果一组顾客都能得到新鲜的甜甜圈,那么这组顾客就是快乐的。也就是说,该组的第一个顾客没有收到上一组剩下的甜甜圈。

你可以自由地重新安排组的顺序。返回重新安排组后可能的最多快乐组数。

示例 1:

输入:batchSize = 3, groups = [1,2,3,4,5,6]
输出:4
解释:你可以将组安排为 [6,2,4,5,1,3]。那么第 1、2、4、6 组将是快乐的。

示例 2:

输入:batchSize = 4, groups = [1,3,2,5,2,2,1,6]
输出:4

提示:

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

解题思路

解题思路

这是一个经典的状态压缩动态规划问题。核心思想是将问题转化为分割问题:将所有组分割成若干个分区,使得每个分区内的顾客总数能被 batchSize 整除。

关键观察

  1. 只有余数重要:对于每个组,我们只关心其大小模 batchSize 的余数
  2. 频率统计:统计每种余数出现的频次
  3. 配对优化:余数为 r 的组可以和余数为 batchSize - r 的组配对,形成一个完整的快乐分区

算法步骤

  1. 预处理配对:将可以直接配对的组先处理掉(如余数互补的组)
  2. 状态压缩DP:使用频率数组的哈希值作为状态,记录当前剩余余数和对应的最大快乐组数
  3. 状态转移:对于每种余数,尝试取出一个组,更新状态

复杂度分析

  • 时间复杂度:O(batchSize^groups.length),但由于预处理和剪枝,实际运行时间会小很多
  • 空间复杂度:O(状态数量),用于记忆化

这种方法充分利用了 batchSize ≤ 9 这个限制,使得状态空间可控。

代码实现

class Solution {
public:
    int maxHappyGroups(int batchSize, vector<int>& groups) {
        vector<int> freq(batchSize, 0);
        int result = 0;
        
        // 统计每种余数的频次
        for (int group : groups) {
            freq[group % batchSize]++;
        }
        
        // 余数为0的组天然快乐
        result += freq[0];
        freq[0] = 0;
        
        // 配对处理互补的余数
        for (int i = 1; i <= batchSize / 2; i++) {
            if (i == batchSize - i) {
                result += freq[i] / 2;
                freq[i] %= 2;
            } else {
                int pairs = min(freq[i], freq[batchSize - i]);
                result += pairs;
                freq[i] -= pairs;
                freq[batchSize - i] -= pairs;
            }
        }
        
        unordered_map<string, int> memo;
        return result + dfs(freq, 0, batchSize, memo);
    }
    
private:
    int dfs(vector<int>& freq, int remainder, int batchSize, unordered_map<string, int>& memo) {
        string key = to_string(remainder) + "#";
        for (int f : freq) {
            key += to_string(f) + ",";
        }
        
        if (memo.count(key)) {
            return memo[key];
        }
        
        int maxVal = 0;
        for (int i = 1; i < batchSize; i++) {
            if (freq[i] > 0) {
                freq[i]--;
                int newRemainder = (remainder + i) % batchSize;
                int gain = (remainder == 0) ? 1 : 0;
                maxVal = max(maxVal, gain + dfs(freq, newRemainder, batchSize, memo));
                freq[i]++;
            }
        }
        
        return memo[key] = maxVal;
    }
};
class Solution:
    def maxHappyGroups(self, batchSize: int, groups: List[int]) -> int:
        freq = [0] * batchSize
        result = 0
        
        # 统计每种余数的频次
        for group in groups:
            freq[group % batchSize] += 1
        
        # 余数为0的组天然快乐
        result += freq[0]
        freq[0] = 0
        
        # 配对处理互补的余数
        for i in range(1, batchSize // 2 + 1):
            if i == batchSize - i:
                result += freq[i] // 2
                freq[i] %= 2
            else:
                pairs = min(freq[i], freq[batchSize - i])
                result += pairs
                freq[i] -= pairs
                freq[batchSize - i] -= pairs
        
        memo = {}
        
        def dfs(freq_tuple, remainder):
            if freq_tuple in memo:
                return memo[freq_tuple]
            
            freq_list = list(freq_tuple)
            max_val = 0
            
            for i in range(1, batchSize):
                if freq_list[i] > 0:
                    freq_list[i] -= 1
                    new_remainder = (remainder + i) % batchSize
                    gain = 1 if remainder == 0 else 0
                    max_val = max(max_val, gain + dfs(tuple(freq_list), new_remainder))
                    freq_list[i] += 1
            
            memo[freq_tuple] = max_val
            return max_val
        
        return result + dfs(tuple(freq), 0)
public class Solution {
    public int MaxHappyGroups(int batchSize, int[] groups) {
        int[] freq = new int[batchSize];
        int result = 0;
        
        // 统计每种余数的频次
        foreach (int group in groups) {
            freq[group % batchSize]++;
        }
        
        // 余数为0的组天然快乐
        result += freq[0];
        freq[0] = 0;
        
        // 配对处理互补的余数
        for (int i = 1; i <= batchSize / 2; i++) {
            if (i == batchSize - i) {
                result += freq[i] / 2;
                freq[i] %= 2;
            } else {
                int pairs = Math.Min(freq[i], freq[batchSize - i]);
                result += pairs;
                freq[i] -= pairs;
                freq[batchSize - i] -= pairs;
            }
        }
        
        Dictionary<string, int> memo = new Dictionary<string, int>();
        return result + Dfs(freq, 0, batchSize, memo);
    }
    
    private int Dfs(int[] freq, int remainder, int batchSize, Dictionary<string, int> memo) {
        string key = remainder + "#" + string.Join(",", freq);
        
        if (memo.ContainsKey(key)) {
            return memo[key];
        }
        
        int maxVal = 0;
        for (int i = 1; i < batchSize; i++) {
            if (freq[i] > 0) {
                freq[i]--;
                int newRemainder = (remainder + i) % batchSize;
                int gain = remainder == 0 ? 1 : 0;
                maxVal = Math.Max(maxVal, gain + Dfs(freq, newRemainder, batchSize, memo));
                freq[i]++;
            }
        }
        
        memo[key] = maxVal;
        return maxVal;
    }
}
var maxHappyGroups = function(batchSize, groups) {
    const counts = new Array(batchSize).fill(0);
    let result = 0;
    
    for (let group of groups) {
        counts[group % batchSize]++;
    }
    
    result += counts[0];
    counts[0] = 0;
    
    for (let i = 1; i <= Math.floor(batchSize / 2); i++) {
        if (i === batchSize - i) {
            result += Math.floor(counts[i] / 2);
            counts[i] %= 2;
        } else {
            const pairs = Math.min(counts[i], counts[batchSize - i]);
            result += pairs;
            counts[i] -= pairs;
            counts[batchSize - i] -= pairs;
        }
    }
    
    const memo = new Map();
    
    function dfs(state, remainder) {
        const key = state.join(',') + ',' + remainder;
        if (memo.has(key)) return memo.get(key);
        
        let maxHappy = 0;
        let hasGroups = false;
        
        for (let i = 1; i < batchSize; i++) {
            if (state[i] > 0) {
                hasGroups = true;
                state[i]--;
                const newRemainder = (remainder + i) % batchSize;
                const happy = (remainder === 0 ? 1 : 0) + dfs(state, newRemainder);
                maxHappy = Math.max(maxHappy, happy);
                state[i]++;
            }
        }
        
        if (!hasGroups) maxHappy = 0;
        memo.set(key, maxHappy);
        return maxHappy;
    }
    
    result += dfs(counts, 0);
    return result;
};

复杂度分析

复杂度类型说明
时间复杂度O(batchSize^n)n为剩余未配对的组数,实际由于剪枝效果较好
空间复杂度O(状态数量)记忆化存储的状态数量,最坏情况下为指数级