目录
347. 前 K 个高频元素
题目描述
解题思路
代码实现
77. 组合
题目描述
解题思路
代码实现
347. 前 K 个高频元素
题目描述
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
示例 2:
输入: nums = [1], k = 1 输出: [1]
提示:
1 <= nums.length <= 105
k
的取值范围是[1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前
k
个高频元素的集合是唯一的
进阶:你所设计算法的时间复杂度 必须 优于 O(n log n)
,其中 n
是数组大小。
解题思路
-
排序:首先,对输入的整数数组
nums
进行排序,这样可以方便后续对相同数字进行计数。 -
计数:使用结构体数组
Nums
来存储排序后的数字以及它们的出现次数。遍历排序后的nums
,对于每个数字,如果与前一个数字相同,则增加计数;如果不同,则创建一个新的Num
结构体,并设置计数为 1。 -
再次排序:根据
Num
结构体的cnt
字段(即数字的出现次数)对Nums
进行降序排序。这样,出现次数最多的数字会排在前面。 -
提取结果:从排序后的
Nums
中提取前k
个数字,即出现次数最多的k
个数,存入结果数组res
。 -
返回结果:返回结果数组
res
,并设置returnSize
为k
。
代码实现
/** * Note: The returned array must be malloced, assume caller calls free(). */
struct Num { int num; // 存储数字 int cnt; // 存储数字出现的次数
}; // 比较函数,用于整数数组的排序
int cmp(const void* a, const void* b) { return *(int*)a - *(int*)b;
} // 比较函数,用于Num结构体的排序,按出现次数降序
int cmps(const void* a, const void* b) { return (*(struct Num*)b).cnt - (*(struct Num*)a).cnt;
} int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) { *returnSize = k; int* res = (int*)malloc(sizeof(int) * k); // 分配结果数组内存 struct Num Nums[numsSize]; // 定义结构体数组用于存储数字及其出现次数 int index = 0; // 结构体数组的索引 // 对整数数组进行排序 qsort(nums, numsSize, sizeof(int), cmp); // 初始化第一个Num结构体 Nums[index].num = nums[0]; Nums[index].cnt = 1; // 遍历整数数组,进行计数 for (int i = 1; i < numsSize; i++) { if (nums[i] == nums[i - 1]) { Nums[index].cnt++; } else { index++; Nums[index].num = nums[i]; Nums[index].cnt = 1; } } // 根据出现次数对Nums进行降序排序 qsort(Nums, index + 1, sizeof(struct Num), cmps); // 提取前k个数字 for (int i = 0; i < k; i++) { res[i] = Nums[i].num; } return res;
}
77. 组合
题目描述
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2 输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]
示例 2:
输入:n = 1, k = 1 输出:[[1]]
提示:
1 <= n <= 20
1 <= k <= n
解题思路
一、问题分析
题目要求从n个数字中选出k个数字的所有组合。这是一个典型的组合问题,其中组合是不考虑顺序的。我们需要生成所有可能的组合,并返回这些组合的结果。
二、回溯算法介绍
为了解决这个问题,可以使用回溯算法。回溯算法是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化来丢弃该解,即“回溯”并尝试其他的可能性。
三、定义全局变量
在回溯的过程中,需要一些全局变量来辅助计算。path
数组用于存储当前正在构建的组合路径,result
数组用于存储所有找到的组合结果,pIndex
和 rIndex
分别用于追踪 path
和 result
的索引。
四、编写回溯函数
-
递归终止条件:当
pIndex
(当前路径的长度)等于k
时,说明已经找到了一个完整的组合,将其添加到result
数组中。 -
搜索空间剪枝:我们从
startIndex
开始遍历,确保不会重复选择数字,也不会选择超过剩余需要的数字数。这里startIndex
的取值范围是根据当前路径长度和剩余需要选择的数字数来确定的。 -
递归与回溯:对于每个遍历到的数字,将其添加到
path
中,并递归调用backtracking
函数继续搜索下一个数字。递归返回后,需要撤销选择,即将pIndex
减一,以尝试其他可能性。
五、编写主函数
-
初始化:为
path
和result
分配内存,并初始化pIndex
和rIndex
。 -
调用回溯函数:调用
backtracking
函数开始生成组合。 -
设置返回信息:设置返回的组合数量和大小信息。由于每个组合的大小都是
k
,可以直接用一个数组returnColumnSizes
来存储每个组合的大小信息。 -
返回结果:返回
result
数组作为结果。
代码实现
/*** Return an array of arrays of size *returnSize.* The sizes of the arrays are returned as *returnColumnSizes array.* Note: Both returned array and *columnSizes array must be malloced, assume* caller calls free().*/// 定义全局变量,用于存储当前路径和结果
int* path; // 存储当前正在构建的组合路径
int** result; // 存储所有组合的结果
int pIndex, rIndex; // 分别用于追踪 path 和 result 的索引// 回溯函数,用于生成所有可能的组合
void backtracking(int n, int k, int startIndex) {// 如果当前路径中的数字个数达到了 k,说明找到了一组解if (pIndex == k) {// 分配内存存储当前路径int* tmp = (int*)malloc(k * sizeof(int));// 将当前路径复制到 tmp 中for (int i = 0; i < k; i++) {tmp[i] = path[i];}// 将 tmp 添加到结果数组中result[rIndex++] = tmp;return;}// 从 startIndex 开始遍历到最大可能的起始位置for (int i = startIndex; i <= n - (k - pIndex) + 1; i++) {// 将当前数字添加到路径中path[pIndex++] = i;// 继续递归搜索下一个数字backtracking(n, k, i + 1);// 回溯,撤销选择pIndex--;}
}int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {path = (int*)malloc(k * sizeof(int));result = (int**)malloc(200001 * sizeof(int*));// 初始化索引pIndex = 0, rIndex = 0;// 调用回溯函数开始生成组合backtracking(n, k, 1);// 设置返回的组合数量和大小信息*returnSize = rIndex;*returnColumnSizes = (int*)malloc(rIndex * sizeof(int));// 设置每个组合的大小为 kfor (int i = 0; i < rIndex; i++) {(*returnColumnSizes)[i] = k;}// 返回结果return result;
}