数组标记
一.桶标记
1.认识数组
数组是一种将数据有序存放的结构,通过一维数组的学习,相信同学们会感觉数组的使用是非常灵活的,原因无异于是因为数组里一个下标对应一个元素。例如 int a[5]={2,3,5,7,11},下标0对应的元素为a[0],下标1对应元素a[1]...,那么在将数组应用在生活或者学习场景中时,下标及其对应的元素就会有不同的含义。
2.桶标记引入背景:
这里有一个有趣的问题:从键盘输入5 个0-9 的数,然后输出0-9 中哪些没有出现过的数。例如,输入2 5 2 1 8 时,输出0 3 4 6 7 9。
大家拿到这道题,第一反应一定是:10个计数器就ok啦!那题目如果输入范围是0-99之间的数,好像得100个计数器了,这个工作量有点繁重呀!
别急,我们就先按照这种思路来捋一下代码:
(1)首先声明10个计数器(可以将计数器看成是标记变量,后续不再累述):
bool s0=0,s1=0,s2=0,s3=0,s4=0,s5=0,s6=0,s7=0,s8=0,s9=0;
Copy
说明:
s0表示数字0是否出现过,初值为0,表示0还未出现,如果输入的值是0,那么我们需要将s0的改为1;
s1表示数字1是否出现过,初值为0,表示1还未出现,如果输入的值是1,那么我们需要将s1的改为1;
...
s9表示数字9是否出现过,初值为0,表示9还未出现,如果输入的值是9,那么我们需要将s9的改为1;
(2)输入5个数字,并进行10次判断和赋值
for(i=1;i<=5;i++){
cin >> t;
if(t==0) s0=1;
if(t==1) s1=1;
if(t==2) s2=1;
...
if(t==9) s9=1;
}
Copy
(3)分别判断这10个标记变量的值,如果某一变量的值为0 ,则说明它的对应数字没有出现过,即这个数没有被标记过,可以输出。如果某一变量的值为1,则说明它的对应数字出现过,即这个数被标记过,不能输出。
if(s0==0) cout<<0<<" ";
if(s1==0) cout<<1<<" ";
if(s2==0) cout<<2<<" ";
...
if(s9==0) cout<<9;
Copy
细心地同学可能已经发现了,上述代码里,我们声明的那10个标记变量只有两种值,要不为0,要不为1。没有其他值可取。同学们再思考一下就会理解0和1是对应两种状态的,例如若 s1=0,表示1未被标记,这是数字1没有出现的状态,若s1=1,表示1被标记,这是数字1出现的状态。通过s1的值判断1是否出现。
不过你会发现,这样写的代码只适合判断小范围的数。一旦题目的数据范围变大的话,你声明的变量会很多,而且要写的条件判断会很多,代码量是个大问题。
我们写代码要尽量做到短而简洁,其实我们除了这种方法外还可以利用数组+for循环来简化代码:
【分析】:
(1)10个标记变量的用法一样,初始值也全部都是0,所以可以把他们存放在同一个数组a中。 其中a[0] 代表 s0,a[1] 代表 s1...a[9] 代表s9。同时,整个数组初始值为0,所以:int a[10]={0};
初始值全部为0,表示0-9这些数还全都没有出现过呢。
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|---|
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | a[9] |
(2)for循环不变,依然需要循环输入5个数字并进行标记。即:
for(i=1;i<=5;i++)
{
cin >> t;
if(t==0) a[0]=1;
if(t==1) a[1]=1;
....
if(t==9) a[9]=1;
}
Copy
不难发现,上述for循环部分的if语句满足同一个规律:t的值和进行 =1 操作的变量的下标值是保持一致的,或者说:t就是进行 =1 操作的变量的下标,所以for循环中的10个if语句可以总结为一句:a[t]=1;
(3)对a数组每一个元素进行判断,如果某个a[i]为0,就说明i这个数字没出现过,。如果某个a[i]为1,就说明i这个数字出现过。
说到这里,我们整个代码的构架就已经理清楚了。参考代码如下:
#include<iostream>
using namespace std;
int main()
{
bool a[10]={0};//int 型数组也可以实现,不过本题用bool型数组更贴和实际
int t;
for(int i=1;i<=5;i++)
{
cin>>t;
a[t]=1;//将编号为t的桶值标记为1,表示t这个数出现过
}
for(int i=0;i<=9;i++)
{
if(a[i]==0) //或if(!a[i])
{
cout<<i<<" ";
}
}
return 0;
}
Copy
理解:
cin>>t;a[t]=1;
此时此刻你可以将数组的每个小房子看成一个桶,既然小房子有编号,自然而然桶也是有编号的,而且桶的编号也是从0到9.
若输入的t为5,那么a[5]=1;
元素值 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|---|
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | |
下标/桶编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
a[5]的值已经变成1了,说明编号为5的桶被标记了,即数字5出现了。
记住,判断数字t是否出现过,其实是对数组下标t对应的元素进行判断,即对a[t]进行判断。
3.桶标记简单应用
1.第k小整数
题目描述:
现有n个正整数(n≤10000),要求出这n个正整数中的第k个最小整数(相同大小的整数只计算一次)(k≤100),正整数均不大于300。
输入格式:输入共两行。第一行为n和k; 第二行开始为n个正整数的值,整数间用空格隔开。
输出格式:第k个最小整数的值;若无解,则输出“NO RESULT”。
输入数据#1
11 4
1 2 3 1 2 3 1 2 3 1 2
Copy
输出数据#1
NO RESULT
Copy
输入数据#2
10 3
1 3 3 7 2 5 1 2 4 6
Copy
输出数据#2
3
Copy
【分析】:
(1)“相同大小的数只计算一次“,这就意味着同样大小的数字,只占一个名次,或者说,在给数字排序的过程中需要有“去重”这一步操作。
(2)第k小整数,应当是“去重”之后从小到大数的第k个数字。
(3)综上所述,题目要我们输出的就是1,2,3...300这个范围内第k个你所输入的数字,那我们需要准备数组标记1-300范围内哪些数字出现了(不必关心出现的次数),然后准备计数器s,从a[1]到a[300]这个范围内判断当前的a[i]的值,如果a[i]的值非0,就意味着i这个数字出现了,那就s++。
当s==k时,当前的i就是第k小整数。
如果整个a数组遍历完之后的s值小于k,就说明不存在第k小整数。
【参考代码】:
方法一:
#include<bits/stdc++.h>
using namespace std;
int a[301];//a数组作为计数器
int main()
{
int n,k,t,s=0;//s统计当前数字是第几小
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>t;
a[t]=1;
//这里a[t]=1和a[t]++;的操作都可,因为我们只需要知道哪些数字出现了即可,不关心出现次数
}
for(int i=1;i<=300;i++)
{
if(a[i])//或者if(a[i]==1)
{
s++;
if(s==k)
{
cout<<i;
break; //找到即可结束循环,没必要继续找下去了
}
}
}
if(s<k) { cout <<"NO RESULT";}
return 0;
}
Copy
方法二:
int a[301];//a数组作为计数器
int main()
{
...//前半部分代码与方法一一样,只在输出的时候稍微变化一下
for(int i=1;i<=300;i++)
{
if(a[i])//或者if(a[i]==1)
{
s++;
if(s==k)
{
cout<<i;
return 0;
}
}
}
cout <<"NO RESULT";
return 0;
}
Copy
2.校门外的树1122
【题目描述】
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。
【输入】
第一行有两个整数L(1 ≤ L ≤ 10000)和 M(1 ≤ M ≤ 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
对于20%的数据,区域之间没有重合的部分;对于其它的数据,区域之间有重合的情况。
【输出】
包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
输入:500 3 150 300 100 200 470 471
Copy
输出:298
Copy
正在上传…重新上传取消
分析:
1.这道题首先可以在纸上画出一个数轴,根据题意发现,只有整数点上有树,那么我们可以将这些整数点看成是数组的下标,即整数点0(坐标为0的点)与数组下标0对应,整数1(坐标为1的点)与数组下标1对应...,那么可以声明一个数组a[10001]={0},数组长度根据L的范围来确定,全部清0,表示从0—1000点上每个点都有一颗树。
2.一个坐标点t有两种状态,要不有树,要不没有树,刚才我们已经将数组清0表示每个点都有树了,那么如果坐标点t上的树移走了,是不是可以将a[t]赋值为1,即a[t]=1;表示坐标点t已经被标记,后续判断表示没有树了。
3.由于需要移走某些区域的树,那么就说明需要用循环将区间[l,r]间的每个点进行标记。
即for(i=l;i<=r;i++)a[i]=1;循环n次即可。
例如将一个区间[5,7]的树全部移走;
一开始这些点的都有树。
元素值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|---|
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | a[6] | a[7] | a[8] | |
下标/桶编号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
通过循环进行标记
即for(i=5;i<=7;i++)a[i]=1;
5-7点上的数就被标记成1了。
元素值 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|
< |