第十八章:一文掌握数组标记

本文详细介绍了数组标记的概念及其在不同问题中的应用,包括使用桶标记来找出未出现的数字、第k小整数的计算、校门外的树问题以及判断素数等。此外,文章还探讨了桶计数方法,如用于统计数字出现次数、解决页码计数问题和找出最大赢家等。通过实例和代码解析,展示了数组标记和桶计数在解决数学和编程问题中的高效性。
摘要由CSDN通过智能技术生成

 数组标记

一.桶标记

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
<
  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值