← 目录 / 算法文档 · 模块二 基础算法思想 / 2.2 数组标记法

2.2 数组标记法

用一个数组当"小本本",随手记下每个数字的状态——出现过没有、出现了几次、被标记过没有。

本页目录
① 什么是数组标记法

很多问题需要反复"查一下某个数字之前出现过没有""数一下某个数字出现了几次"。如果每次都要重新扫一遍所有数据去查,会很慢——更聪明的办法是:开一个数组,用下标对应数值本身,把"这个数值的状态"直接存在对应的格子里。要查的时候,直接用数值当下标去访问数组,一步到位,不用再扫一遍。

其实 1.8 节的筛法已经用过这个思路了——isComposite 数组和 minPrimeFactor 数组,下标就是数字本身,格子里存的是"这个数被标记过没有""这个数的最小质因子是谁"。这一节把这个技巧从筛法里单独拎出来,看看它还能解决哪些问题。

② 用数组统计出现次数

最常见的用法:统计每个值各出现了多少次。比如统计字符串 "hello world" 里每个字母出现的次数——开一个大小为 26 的数组,下标 0~25 分别对应 a~z,每遇到一个字母,就把对应格子的计数加一。

C++ · 统计字符串中每个字母的出现次数
1string s = "hello world";
2int count[26] = {0}; // 26个字母各自的计数器,初始全是0
3for (char ch : s)
4{
5 if (ch >= 'a' && ch <= 'z') // 跳过空格等非字母字符
6 {
7 count[ch - 'a']++; // 字符转下标,对应格子加一
8 }
9}
📌
ch - 'a' 这一步眼熟吗?1.6 节进制转换时用过几乎一样的写法(s[i] - '0' 把字符变成数字)。这里同样是利用字符本身的编码顺序——'a''z' 在内存里是连续排列的,相减就能得到"这是第几个字母",直接拿来当数组下标。
"hello world" 各字母出现次数统计结果
d
1 次
e
1 次
h
1 次
l
3 次
o
2 次
r
1 次
w
1 次
扫一遍字符串,每个字母只需要 O(1) 的时间(一次减法、一次加一)就能把计数加上去——不管字符串多长,统计的总时间都只和字符串长度成正比,不需要"每个字母再去数一遍整个字符串"那种更慢的写法。
③ 用数组标记"是否出现过"

有时候不需要数"出现了几次",只需要知道"出现过没有"——这时候数组里存的不是计数,而是一个简单的 true/false。经典场景:判断一个数组里有没有重复的数字

C++ · 找出数组中第一个重复的数字
1int arr[] = {3, 1, 4, 1, 5, 9, 2, 6};
2bool seen[10] = {false}; // 数组里的数字都在0~9之间
3for (int x : arr)
4{
5 if (seen[x]) // 这个数之前已经出现过
6 {
7 cout << "第一个重复的数字是: " << x << endl;
8 break;
9 }
10 seen[x] = true; // 标记这个数已经出现过
11}
扫描 {3, 1, 4, 1, 5, 9, 2, 6},seen 数组逐步被填充
扫到 3、1、4:分别标记 seen[3]、seen[1]、seen[4] 为 true
0
0
1
1
0
2
1
3
1
4
0
5
再扫到第二个 1:seen[1] 已经是 true 了 —— 找到重复!
0
0
1
1
0
2
1
3
1
4
0
5
数组下标对应"数值本身",格子里的内容对应"这个数值出不出现过"——查一个数有没有出现过,直接访问 seen[x] 就行,不需要把整个数组从头扫一遍去比较,这正是数组标记法比"逐个比较"快的原因。
④ 应用:找出缺失的数字

再看一个稍微反过来用的例子:18 之间本该有 8 个不同的数,现在给出了其中 7 个:1 2 4 5 6 7 8,找出缺的是哪一个。思路是:先把"出现过的数"都标记一下,再扫一遍 18没被标记过的那个就是答案。

C++ · 找出 1~n 之间缺失的数字
1int n = 8;
2int nums[] = {1, 2, 4, 5, 6, 7, 8}; // 缺了一个数
3bool appeared[9] = {false};
4
5// 第一遍:把出现过的数字都标记一下
6for (int x : nums)
7{
8 appeared[x] = true;
9}
10
11// 第二遍:找出没被标记过的那个数
12for (int i = 1; i <= n; i++)
13{
14 if (!appeared[i])
15 {
16 cout << "缺失的数字是: " << i << endl;
17 }
18}
appeared 数组标记完之后的样子(下标 1~8)
1
2
3
4
5
6
7
8
第二遍扫描 1~8,发现下标 3 对应的格子没被标记——缺失的数字就是 3。这种"先标记、再扫一遍找空缺"的两段式写法,是数组标记法非常典型的应用模式。
⑤ 完整代码与小结
用法数组存的内容典型场景
计数标记整数(出现的次数)统计字母/数字出现频率
布尔标记true / false(出现过没有)判断重复、找缺失的数字
筛法标记true / false 或具体数值(如最小质因子)1.8 节的埃氏筛、线性筛
🎯
数组标记法的核心永远是同一句话:把数值本身当成数组下标,用"一次访问"代替"扫一遍比较"。这个技巧贯穿了从 1.8 节筛法到这一节统计/查重,再到后面模块会用到的 visited 数组(标记搜索时哪些位置已经走过)——记住这个思路,比记住某一道具体题目的代码更重要。