Easy

题目描述

我们买了一些糖果 candies,打算把它们分给排成一排的 n = num_people 个人。

给第一个人 1 颗糖果,第二个人 2 颗,依此类推,直到给第 n 个人 n 颗糖果。

然后,我们再回到队伍的起点,给第一个人 n + 1 颗糖果,第二个人 n + 2 颗,依此类推,直到给第 n 个人 2 * n 颗糖果。

重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次回到队伍起点),直到我们分完所有的糖果。注意,就算最后剩下的糖果数量不足以按照规律继续分发,我们也要尽可能的分完这些糖果。

请你返回一个长度为 num_people、元素之和为 candies 的数组,来表示糖果的最终分发情况(即 ans[i] 表示第 i 个人分到的糖果数)。

示例 1:

输入:candies = 7, num_people = 4
输出:[1,2,3,1]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0,0]。
第三次,ans[2] += 3,数组变为 [1,2,3,0]。
第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。

示例 2:

输入:candies = 10, num_people = 3
输出:[5,2,3]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0]。
第三次,ans[2] += 3,数组变为 [1,2,3]。
第四次,ans[0] += 4,最终数组变为 [5,2,3]。

提示:

  • 1 <= candies <= 10^9
  • 1 <= num_people <= 1000

解题思路

这道题有两种解法:暴力模拟数学优化

方法一:暴力模拟 直接按照题目描述进行模拟。我们维护一个当前要分发的糖果数量(从1开始),以及当前要分发给的人的索引。每次分发时,如果剩余糖果数量大于等于当前应该分发的数量,就正常分发;否则将所有剩余糖果都给当前的人。时间复杂度为 O(√candies),因为总共要进行大约 √(2*candies) 次分发。

方法二:数学优化(推荐) 通过数学公式直接计算每个人应该得到的糖果数量。假设我们能够完整进行 k 轮分发,每轮给每个人分发的糖果数量形成等差数列。我们可以先计算出能够完整进行多少轮,然后用等差数列求和公式计算每个人在完整轮次中得到的糖果,最后处理剩余的糖果。

对于第 i 个人(0索引),在第 j 轮中得到的糖果数量为 j * num_people + i + 1。如果能完整进行 k 轮,那么第 i 个人总共得到的糖果数量为等差数列求和:首项 a1 = i + 1,公差 d = num_people,项数 k

两种方法都能通过,但数学优化方法时间复杂度更优为 O(num_people)。

代码实现

class Solution {
public:
    vector<int> distributeCandies(int candies, int num_people) {
        vector<int> result(num_people, 0);
        
        // 计算能够完整分发多少轮
        int sum = num_people * (num_people + 1) / 2;
        int k = 0;
        while ((long long)(k + 1) * sum + (long long)k * k * num_people <= candies) {
            k++;
        }
        
        // 计算每个人在完整轮次中得到的糖果
        for (int i = 0; i < num_people; i++) {
            result[i] = k * (i + 1) + k * (k - 1) / 2 * num_people;
        }
        
        // 减去已经分发的糖果
        candies -= k * sum + k * k * num_people;
        
        // 处理剩余的糖果
        for (int i = 0; i < num_people && candies > 0; i++) {
            int give = min(candies, k * num_people + i + 1);
            result[i] += give;
            candies -= give;
        }
        
        return result;
    }
};
class Solution:
    def distributeCandies(self, candies: int, num_people: int) -> List[int]:
        result = [0] * num_people
        
        # 计算能够完整分发多少轮
        sum_per_round = num_people * (num_people + 1) // 2
        k = 0
        while (k + 1) * sum_per_round + k * k * num_people <= candies:
            k += 1
        
        # 计算每个人在完整轮次中得到的糖果
        for i in range(num_people):
            result[i] = k * (i + 1) + k * (k - 1) // 2 * num_people
        
        # 减去已经分发的糖果
        candies -= k * sum_per_round + k * k * num_people
        
        # 处理剩余的糖果
        for i in range(num_people):
            if candies <= 0:
                break
            give = min(candies, k * num_people + i + 1)
            result[i] += give
            candies -= give
        
        return result
public class Solution {
    public int[] DistributeCandies(int candies, int num_people) {
        int[] result = new int[num_people];
        
        // 计算能够完整分发多少轮
        long sumPerRound = (long)num_people * (num_people + 1) / 2;
        int k = 0;
        while ((long)(k + 1) * sumPerRound + (long)k * k * num_people <= candies) {
            k++;
        }
        
        // 计算每个人在完整轮次中得到的糖果
        for (int i = 0; i < num_people; i++) {
            result[i] = k * (i + 1) + k * (k - 1) / 2 * num_people;
        }
        
        // 减去已经分发的糖果
        candies -= (int)(k * sumPerRound + (long)k * k * num_people);
        
        // 处理剩余的糖果
        for (int i = 0; i < num_people && candies > 0; i++) {
            int give = Math.Min(candies, k * num_people + i + 1);
            result[i] += give;
            candies -= give;
        }
        
        return result;
    }
}
var distributeCandies = function(candies, num_people) {
    const result = new Array(num_people).fill(0);
    
    // 计算能够完整分发多少轮
    const sumPerRound = num_people * (num_people + 1) / 2;
    let k = 0;
    while ((k + 1) * sumPerRound + k * k * num_people <= candies) {
        k++;
    }
    
    // 计算每个人在完整轮次中得到的糖果
    for (let i = 0; i < num_people; i++) {
        result[i] = k * (i + 1) + Math.floor(k * (k - 1) / 2) * num_people;
    }
    
    // 减去已经分发的糖果
    candies -= k * sumPerRound + k * k * num_people;
    
    // 处理剩余的糖果
    for (let i = 0; i < num_people && candies > 0; i++) {
        const give = Math.min(candies, k * num_people + i + 1);
        result[i] += give;
        candies -= give;
    }
    
    return result;
};

复杂度分析

算法时间复杂度空间复杂度
暴力模拟O(√candies)O(1)
数学优化O(num_people)O(1)

推荐使用数学优化方法,时间复杂度更优。

相关题目