板子:辅助数组计数与标记(空间换时间)
值域已知而且不大时,开一个数组
B,把「值」当成「下标」:B[v] = 1表示v出现过,B[v]++统计出现次数。每次「查一个值有没有出现过」就从变成了 。
代码(2018:未出现的最小正整数)
int findMissMin(int A[], int n) {
int *B = (int *)calloc(n + 2, sizeof(int)); // B[v] = 1 表示 v 出现过,calloc 自动清零
// 下面会读到 B[n+1],所以开 n+2 个
for (int i = 0; i < n; i++)
if (A[i] >= 1 && A[i] <= n) B[A[i]] = 1; // 答案只可能是 1..n+1,<= 0 或 > n 的值不用管,也不会越界
int v = 1;
while (B[v]) v++; // B[n+1] 一定是 0,循环一定会停
free(B);
return v;
}复杂度:时间
关键一步:
例:{-5, 3, 2, 3} → 1;{1, 2, 3} → 4。
通用写法
// 速记
int *B = (int *)calloc(range, sizeof(int)); // 值域是 [0, range)
B[v] = 1; // 标记:v 出现过(查重、找缺失)
B[v]++; // 计数:v 出现了几次(找众数、主元素)
free(B);值可能是负数时,下标加一个偏移量:B[v + OFFSET]。
用在哪
| 题目 | 辅助数组存什么 |
|---|---|
| 2018 未出现的最小正整数 | 标记 |
| 2013 主元素(备选解) | 计数,题目保证 |
| 2015 链表按绝对值去重 | 标记每个绝对值是否出现过 |
| 无序表去重 | 标记 |
| 计数排序 | 先计数,再按下标从小到大输出 |
易错点
- 先想清楚哪些值有用。2018 年这题的关键就是想到答案在
里。 - 数组长度和
有关时要用 malloc或calloc,不要写int B[n](C++ 标准不支持变长数组)。 malloc申请的内存要自己memset清零,calloc自带清零。- 答复杂度时,空间是
,不要顺手写成 。