一、题目
LeeCode 题目链接:1103. 分糖果 II - 力扣(LeetCode)
题目描述:
排排坐,分糖果。
我们买了一些糖果 candies
,打算把它们分给排好队的 n = num_people
个小朋友。
给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n
颗糖果。
然后,我们再回到队伍的起点,给第一个小朋友 n + 1
颗糖果,第二个小朋友 n + 2
颗,依此类推,直到给最后一个小朋友 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
二、解题思路&代码实现
方案一:暴力循环求解
解题思路:
最直观的方法是不断地遍历数组,如果还有糖就一直分,直到没有糖为止。
复杂度分析:
- 时间复杂度:O(n+candies)。循环 O(candies) 次,理由见方法二。
- 空间复杂度:O(1)。返回值不计入。
代码实现:
golang:
func distributeCandies(candies int, num_people int) []int {
ans := make([]int, num_people);
i := 0;
for candies != 0 {
num := min(candies,i+1)
ans[i%num_people] += num
candies -= num
i++
}
return ans
}
PHP:
class Solution {
/**
* @param Integer $candies
* @param Integer $num_people
* @return Integer[]
*/
function distributeCandies($candies, $num_people) {
$i = 0;
$ans = array_fill(0, $num_people, 0);
while($candies > 0){
$ans[$i%$num_people] += min($candies, $i+1);
$candies -= min($candies, $i+1);
$i++;
}
return $ans;
}
}
方案二:等差数列
解题思路:
用数学公式等差数列来实现。
按照顺序分配的糖果,除最后一次不够的情况外,构成公差为1的等差数列,且每个小朋友每次分到的糖果数量也构成公差为numPeople的等差数列。
复杂度分析:
-
时间复杂度:O(N),计算 N 个人的糖果数量。
-
空间复杂度:O(1),除了答案数组只需要常数空间来存储若干变量。
代码实现:
golang:
func distributeCandies(candies int, numPeople int) []int {
last := int((math.Sqrt(float64(candies*8+1)) - 1) / 2) // 可以按照等差数列分配的最后数量
turns := last / (numPeople) // 完整轮数
ans := make([]int, numPeople)
for i := range ans {
ans[i] = (i + 1 + i + 1 + (turns-1)*numPeople) * turns / 2
// 部分靠前的小朋友可能多一轮
if i < last%numPeople {
ans[i] += i + 1 + (turns)*numPeople
}
}
// 若最终无法按等差数列分配,则剩余给最后轮到的小朋友
ans[last%numPeople] += candies - (1+last)*last/2
return ans
}
func distributeCandies(candies int, num_people int) []int {
n := num_people
// how many people received complete gifts
p := int(math.Sqrt(float64(2 * candies) + 0.25) - 0.5)
remaining := candies - int(float64((p + 1) * p) * 0.5)
rows, cols := p/n, p%n
d := make([]int, n)
for i := 0; i < n; i++ {
// complete rows
d[i] = (i + 1) * rows + int(float64(rows * (rows - 1) * n) * 0.5)
// cols in the last row
if i < cols {
d[i] += i + 1 + rows*n
}
}
// remaining candies
d[cols] += remaining
return d
}
方案三:二分查找
解题思路:
整个过程可以分为2部分:第一步所有人都能分到糖果,第二步只有部分人可以分到糖果(也可以是没有人)。假设第一步有round轮,第一步第i(0<=i<round)轮第j(1<=j<=num_people>>)个人可以获得的糖果是确定的:i * num_people + j,因此可以得知第一步需要糖果数为:f(round)=(num_peopleround)(num_people*round+1)/2。只要求出满足f(x)<=candies<f(x+1)的x值,即可求出第一步后,每个人获得的糖果数,第二步只需要按照第round+1轮,正常分配剩余的糖果即可。
复杂度分析:
代码实现:
golang:
func distributeCandies(candies int, num_people int) []int {
//(n*r)*(n*r+1)/2 <= candies < (n*r+n)*(n*r+n+1)/2
sum := candies << 1
ans := make([]int, num_people)
rnd := 0
for num_people * (rnd + 1) * (num_people * (rnd + 1) + 1) < sum {
plus := 1
for num_people * (rnd + plus << 1) * (num_people * (rnd + plus << 1) + 1) < sum {
plus <<= 1
}
rnd += plus
}
candies -= num_people * rnd * (num_people * rnd + 1) / 2
// rnd--
for k := range ans {
ans[k] = (k + 1) * rnd + rnd * (rnd - 1) * num_people >> 1
// ans[k] = (k + 1) * (rnd + 1) + rnd * (rnd + 1) * num_people >> 1
}
// rnd++
for k := range ans {
add := rnd * num_people + k + 1
if candies > add {
candies -= add
ans[k] += add
} else {
ans[k] += candies
break
}
}
return ans
}