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) |
推荐使用数学优化方法,时间复杂度更优。