题目
Alice 有 n 枚糖,其中第 i 枚糖的类型为 candyType[i]
。Alice 注意到她的体重正在增长,所以前去拜访了一位医生。
医生建议 Alice 要少摄入糖分,只吃掉她所有糖的 n / 2 即可(n 是一个偶数)。Alice 非常喜欢这些糖,她想要在遵循医生建议的情况下,尽可能吃到最多不同种类的糖。
给你一个长度为 n 的整数数组 candyType
,返回:Alice 在仅吃掉 n / 2 枚糖的情况下,可以吃到糖的 最多 种类数。
示例 1:
输入:candyType = [1,1,2,2,3,3]
输出:3
解释:Alice 只能吃 6 / 2 = 3 枚糖,由于只有 3 种糖,她可以每种吃一枚。
示例 2:
输入:candyType = [1,1,2,3]
输出:2
解释:Alice 只能吃 4 / 2 = 2 枚糖,不管她选择吃的种类是 [1,2]、[1,3] 还是 [2,3],她只能吃到两种不同类的糖。
示例 3:
输入:candyType = [6,6,6,6]
输出:1
解释:Alice 只能吃 4 / 2 = 2 枚糖,尽管她能吃 2 枚,但只能吃到 1 种糖。
提示:
n == candyType.length
2 <= n <= 10^4
n
是一个偶数-10^5 <= candyType[i] <= 10^5
代码
解法1 排序后查找(费时间省空间)
#include <stdlib.h>
#include <stdio.h>
int cmp(const int *a, const int *b)
{
return (*(int*)a - *(int*)b);
}
int distributeCandies(int* candyType, int candyTypeSize) {
int res = 0;
int lastEatType = -1;
qsort(candyType,candyTypeSize,sizeof(int),cmp);
for (int i = 0; i < candyTypeSize; i++)
{
if(candyType[i] != lastEatType)
{
lastEatType = candyType[i];
res++;
}
if(res >= candyTypeSize / 2)
{
break;
}
}
return res;
}
int main(void)
{
int a[]= {10000,0,10000,0,10000,0,10000,0,10000,0,10000,0};
int size = sizeof(a) / sizeof(a[0]);
int ret = distributeCandies(a, size);
printf("ret = %d\n",ret);
}
代码复杂度分析
-
时间复杂度:
- 主要的计算成本来自
qsort
函数,该函数对candyType
数组进行排序。qsort
的平均时间复杂度是(O(nlog n))
,其中 (n) 是candyTypeSize
。 - 排序之后,代码遍历排序后的数组一次。这个循环的时间复杂度是
(O(n))
。 - 因此,函数的总体时间复杂度主要由排序步骤决定,为
(O(nlog n))
。
- 主要的计算成本来自
-
空间复杂度:
- 空间复杂度主要由
qsort
函数决定,通常需要(O(log n))
的额外空间用于递归栈。 - 除此之外,只使用了几个整数 (
res
,lastEatType
, 循环索引i
),这些是(O(1))
。 - 因此,整体空间复杂度是
(O(log n))
。
- 空间复杂度主要由
代码拆解
以下是代码的逐步拆解:
-
比较函数:
int cmp(const int *a, const int *b) { return (*(int*)a - *(int*)b); }
- 这是一个比较函数,用于
qsort
。它接收两个整数指针作为参数,返回它们的差值,用于决定排序顺序。
- 这是一个比较函数,用于
-
分发糖果函数:
int distributeCandies(int* candyType, int candyTypeSize) { int res = 0; int lastEatType = -1; qsort(candyType, candyTypeSize, sizeof(int), cmp);
distributeCandies
函数接收一个整数数组candyType
和数组的大小candyTypeSize
。- 初始化结果
res
为 0 和lastEatType
为 -1。 - 使用
qsort
对candyType
数组进行排序。
-
遍历排序后的数组:
for (int i = 0; i < candyTypeSize; i++) { if(candyType[i] != lastEatType) { lastEatType = candyType[i]; res++; } if(res >= candyTypeSize / 2) { break; } } return res; }
- 遍历排序后的数组:
- 如果当前糖果类型与上一个吃过的糖果类型不同,则更新
lastEatType
为当前糖果类型,并将res
加1。 - 如果
res
达到或超过糖果总数的一半,则跳出循环。
- 如果当前糖果类型与上一个吃过的糖果类型不同,则更新
- 返回
res
,即可以分发的不同糖果类型的数量。
- 遍历排序后的数组:
结果
解法2 用数组下标查找(费空间省时间)
int distributeCandies(int* candyType, int candyTypeSize) {
int res = 0;
int typeArr[200000] = {0}; // type从-10000 到10000
for (int i = 0; i < candyTypeSize; i++)
{
if(typeArr[candyType[i]+100000 - 1] == 0) // -1防止越界
{
typeArr[candyType[i]+100000 - 1] = 1;
res ++;
}
if(res >= candyTypeSize / 2)
{
break;
}
}
return res;
}
代码复杂度分析
-
时间复杂度:
- 代码遍历数组一次。循环的时间复杂度是
(O(n))
。
- 代码遍历数组一次。循环的时间复杂度是
-
空间复杂度:
- 空间复杂度来自于typeArr数组,题目规定范围是-10^5 ~ 10^ 5,因此也可以看作
(O(1))
,其他的变量都是(O(1))
- 因此对于这一题,这个复杂度也是
(O(1))
。
- 空间复杂度来自于typeArr数组,题目规定范围是-10^5 ~ 10^ 5,因此也可以看作
结果
不出所料地比第一种解法优
总结
- 该函数通过排序和遍历来计算可以分发的最大不同糖果类型数量。
- 关键步骤是使用
qsort
进行排序,然后遍历已排序数组,确保每种糖果类型只计数一次,直到达到最大可分发数量。 - 如果对题目分析透彻一点,投机取巧可以省下时间和空间