05 | 数组:一秒钟,定义 1000 个变量

你好,我是胡光,咱们又见面了。通过前几节的学习,你已经了解了基本的程序结构。我们来简单总结一下,其中第一种结构就是顺序结构,它指的是我们所写的按照顺序执行的代码,执行完上一行,再执行下一行这种的。第二种就是分支结构,主要是用 if 条件分支语句来实现,主要特征是根据表达式的真假,选择性地执行后续代码。最后一种就是循环结构,用来重复执行某段代码的结构。

如果把程序比喻成工厂的话,现在你的工厂中已经有了各种各样的流水线,但这个工厂只是能生产产品还不行,还需要有存储的空间。今天,我们来学习的就是如何创建和使用工厂中的库房,本节之后,你的程序工厂就可以开工了!

今日任务

先来看看今天这10分钟的小任务吧。今天的任务是这样的,程序中读入一个整数 n,假设 n 不会大于 1000,请输出 1 到 n 的每一个数字二进制表示中的 1 的个数。

我举个例子哈,当 n 等于 7 的时候,我们把 1 到 7 的每个数字的二进制表示罗列出来,会得到下表所示内容:

根据表1中的内容,如果你的程序编写成功的话,程序应该分别输出1、1、2、1、2、2、3,这些输出内容分别代表每个数字二进制表示中 1 的数量。

对于这个任务,你想写出来一个可行的程序不难,例如:我们可以循环 n 次,每次计算一个数字二进制中 1 的数量。怎么计算一个数字二进制中 1 的数量呢?这个问题,你可能想采用如下程序来进行实现:

int cnt = 0;
while (n != 0) {
    if (n % 2 == 1) cnt += 1; 
    n /= 2; 
}

我解释下上面这段程序,它每次都会判断 n 的二进制末尾是不是 1,如果是 1,计数量 cnt 就加 1(+=表达式,我这里就不解释了,如果你不理解,可以自己查下),然后将 n 除以2,相当于去掉 n 的二进制表示的最后一位,这样就可以用 O(logn) 的时间复杂度(关于这个知识点,你也可以自行查阅相关资料,其实很简单)计算一个数字 n 二进制中 1 的数量。

以二进制数字110为例,末尾是0,计数量 cnt 不进入计算;然后使用二进制除法,让110除以2,即去掉最后一位的0,变成了11,此时末尾是1,计数量cnt 就加1;11再除以2,变成了1,此时末尾是1,计数量cnt 再次加1 。最后的n等于1,再除以2,n变成了0,循环结束。

可以看到,当我们输入数字6,二进制的表示是110时,整个程序中计数量cnt 共计算了2次,所以最后的输出结果是2 。

关于时间复杂度这个概念,后续我们还会进一步介绍,现在你可以简单地理解成为是程序运行的次数,例如 n=8 的时候,上面循环执行 3 次,也就是 log 以 2 为底 8 的对数的值。

如果你的方法像上面这么做的话,确实是一种可行的方法,可是效率不是很高。今天这个任务的要求是,对每一个数字,请用 O(1) 的时间复杂度计算得到其二进制表示中 1 的个数。O(1) 也就是 1 次,或者是与问题规模 n 无关的有限次,例如:2次、3次均可。下面就让我们来看看如何完成这个任务吧。

必知必会,查缺补漏

1.数组:规模化存储工具

我要给你介绍的第一个帮助我们完成今天任务的工具是:数组。所谓数组,你可以把这两个字对调过来理解,即组数,一组数据。

以往我们定义的变量,都是单一变量,例如:一个整型变量,一个浮点型变量,等等。可当我们要同时记录 n 个整型数据的时候,通过以往的知识,你能实现这个需求么?注意,这个里面的 n 是通过读入的一个变量,通常情况会有一个最大范围,例如:n 不会超过1000。你总不能定义1000个整型变量吧?

面对上面这种需求,数组就派上了用场,利用数组,我们可以定义存放一组数据的存储区,用法如下代码所示:

int arr[1000];

通过上述代码,我们很轻松的就定义了存储 1000 个整型变量的存储区 arr。这里相当于向计算机申请了可以存储1000个整型变量的存储空间。第一个存储整型数据的内存空间,也就是第一个整型变量,就是 arr[0],第二个整型变量是 arr[1],以此类推。arr 后面方括号里面的东西,我们称之为“数组下标”,数组下标从 0 开始,也就是说,代表 1000 个整型变量的数组,下标范围应该是 0 到 999,具体可以参考图1。

有了数组以后,你就可以轻松的完成读入 n 个整型数据的任务了,参考代码如下:

int n, arr[1000];
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &arr[i]);

代码中,第一行定义了一个整型变量 n 和一个最多存储 1000 个整型元素的数组空间。第二行接下来读入 n 的值,第三行利用循环结构循环 n 次,循环变量 i 取值从 0 到 n-1,循环每次读入一个整型数据存放在 arr[i] 里面。

这样一段程序执行完后,n 个整型数据就被依次的存放在了 arr[0] 到 arr[n-1]中。当你想在程序中使用第三个整型数据的时候,只需要访问 arr[2] 即可。当然,上述循环变量的取值范围也可以调整到 1 到 n,这样做的话,相当于我们将 n 个整型数据存放在了 arr[1]到 arr[n] 处。

2.字节与地址:数据的住所和门牌号

在之前第2篇的学习中,不知道你还记不记得一个叫做 char 的数据类型,我们称其为字符型。当时在学习的时候,我们说,字符型数据形如:“a”“b”“c”“+”“-” 等被引号包裹着的内容。这次我将带你从 char 类型开始,深入理解两个概念:字节与地址。

什么是字节呢?它是计算机中最基本的存储单位,就像一个一个不可分割的小格子一样,存在于我们计算机的内存中。例如,我们通常所说的,一个32位整型元素占用4个字节,那就意味着这个元素需要占用4个小格子,不会存在某个元素占用 0.5 个小格子的情况。这就是所谓的不可分割。

任何类型的元素,整型也好,浮点型也罢,只要是想存储在计算机中,就一定要放在这些小格子里面,唯一的区别,就是每一种类型的元素占用的格子数量不一样。例如:32位整型占4个格子,double 双精度浮点型占 8 个格子。在这里,需要注意的是,每一种基础类型,在内存中存储时,一定是占用若干个连续的存储单元。

那么如何查看某个类型的元素究竟占用多大的存储空间呢?可以使用 sizeof 这个运算符,如下:

int a;
sizeof(a); // 计算 a 变量占用字节数量
sizeof(int); // 计算一个整型元素占用字节数量

正如你所看到的,sizeof 的使用,就像函数方法一样,我们想要查看什么元素或者类型所占用字节数量,就把什么传入 sizeof 即可,你可以使用 printf 语句输出 sizeof 表达式的值以查看结果。

了解了什么是字节以后,下面我们就要说一个更小的单位了,叫做比特,英文是 bit。这个是计算机中表示数据的最小单位。对比字节是存储数据的最基本单位,比特是表示信息的最基本单位。

那什么又是比特呢?在其他参考资料上你可能知道,计算机里面的所有数据,均是用二进制来表示以及存储的,这里需要注意,是所有的。那么一个比特,就是一个二进制位,要么是 0,要么是 1。8比特位是 1 个字节,那么我们之前所说的32位整型,也就是占32个比特位的整数类型,换算一下,正好是占 4 个字节。

说完了字节的概念后,我们再来说说地址。

现在我们的一些小区里面都有一个集中式的邮箱,邮递员来投递信件的时候,只需要把信件放到相应的邮箱里面即可。而作为住户,会有一把能打开自己家邮箱的钥匙,找到自己的邮箱,取出信件即可。

如果把这个场景放在计算机中,住户其实就是 CPU,而邮箱就是内存。你会发现,住户之所以可以准确找到自己的邮箱,是因为每个邮箱上面有一个独立编号。那么 CPU 能够准确找到程序所需要数据的本质原因,也是因为每一个字节都有一个独立的编号,我们管这个编号,叫做:内存地址!下面我给你放了一张示意图:

上图中,下面空白的格子就是我们所谓的字节,具体的数据信息,就是存储在这些格子里面的,格子上面的是十六进制数字,就是我们所谓的地址,你会看到,在内存中,字节的地址是连续的。

最后我们来总结一下,比特是数据表示的最小单位,就是我们通常所说的一个二进制位。字节是数据存储的最基本单位,存储在计算机中的数据,一定是占用若干个字节的存储空间。最后就是内存地址,是每一个字节的唯一标记。

3.直观感受:内存地址

你可能会觉得内存地址是一个很抽象的概念,不具体。其实我们可以像输出整型值一样,把内存地址也输出出来。

你还记得格式占位符的作用吧?不同数据类型,用不同的格式占位符输出,例如:%d 对应了十进制整型的输出。内存地址则采用 %p 这个格式占位符进行输出,下面给你一个演示程序,你可以在你的环境中运行一下:

#include <stdio.h>
int main() {
    int a;
    printf("%p\n", &a); // 输出 a 变量的地址
    return 0;
}

代码中,首先定义一个了整型变量 a,然后使用 %p 占位符输出 a 变量的地址。单一的 & 运算符放到变量前面,取到的就是这个变量的首地址

为什么说是首地址呢?上一部分说了,一个32位整型变量会占用4个字节的存储空间,每一个字节都会有一个地址,那么你会发现,上面程序中的 a 变量实际上有 4 个地址,这 4 个地址究竟哪一个作为 a 变量的地址呢?答案是最靠前的那个地址,作为 a 变量的地址,也就是这个变量的首地址。

看到了变量的地址信息以后,下面就让我们来看一看与数组相关的地址信息,看下面这段程序:

#include <stdio.h>
int main() {
    int arr[100];
    printf("&arr[0] = %p\n", &arr[0]); // 输出 arr[0] 的地址
    printf("&arr[1] = %p\n", &arr[1]); // 输出 arr[1] 的地址
    printf("    arr = %p\n", arr);     // 输出 arr 的信息
    return 0;
}

上述代码,会输出三行信息,针对这三行信息,每个人的程序运行出来的结果很可能是不一样的,这一点没关系,可你一定会发现如下规律:第一个地址与第二个地址之间差4字节,而输出的第三个地址与第一个地址完全相同。

下面我就来解释一下这两个现象。

  • 第一,数组的每个元素之间在内存中是连续存储的,也就是对上面程序中的数组而言,第一个元素占头4个字节,第二个元素紧接着占接下来的4个字节的存储空间。再结合上面说到的变量首地址的概念,你就很容易理解为什么头两个地址之间差4了。

  • 第二,在程序中,当我们单独使用数组名字的时候,实际上就代表了整个数组的首地址,整个数组(arr[100])的首地址就是数组中第一个元素的首地址,也就是 arr[0] 的地址。

在这里,我们来进一步看一下这个等价关系,arr 等价于 &arr[0](取地址 arr[0]),实际上我们的地址也是支持+/-法的,也就是 arr + 0 等价于 arr[0] 的地址,那么 arr[1] 的地址等于 arr 加几呢?

你可能会认为是加 4,这种直觉还是值得鼓励的,可结果不正确,这个和地址的类型有关系,后面讲到指针的时候,我再详细的讲给你听。不过,事实上,arr + 1 就等价于 arr[1] 的地址,更一般的 arr + i 就等价于 arr[i] 的地址。关于地址上的+/-运算的规则,我在后续的文章中会详细进行讲解。

4.再看 scanf 函数:其实我是一个“邮递员”

有了上面对于地址的基本认识以后,我们再来回顾一下 scanf 函数的用法,你可能会有新的收获,看如下读入程序:

#include <stdio.h>
int main() {
    int a;
    scanf("%d", &a);
    return 0;
}

上面这个程序,就是一个最简单的读入程序,首先定义一个整型变量 a,然后读入一个整数,存储到 a 中。

学习完了地址以后,你就会意识到,我们传给 scanf 函数的,不是 a 变量,准确来说,而是 a 变量的地址。

为什么要把 a 变量的地址传递给 scanf 函数呢?这个很好理解,你就把 scanf 函数当成邮递员,邮递员得到了信件以后,需要知道这个数据放到哪个邮箱里面啊,而你需要做的就是把邮箱地址告诉这个邮递员即可,就是变量 a 的地址,这样 scanf 函数就能把获得的数据,准确的放到 a 变量所对应的内存单元中了。

一起动手,搞事情

思考题:去掉倍数

设计一个去掉倍数的程序,要求如下:
首先读入两个数字 n 和 m,n 的大小不会超过10,m 的大小都不会超过 10000;
接下来读入 n 个各不相同的正整数,输出 1 到 m 中,有哪些数字无法被这 n 个正整数中任意的一个整除。

下面给出一组输入和输出的样例,以供你来参考。

输入如下:

3 12
4 5 6

输出如下:

1 2 3 7 9 11

用数组,做递推

有了对数组的基本认识之后,就让我们来看一下今天的任务应该如何求解。请你观察下面的位运算性质:

y = x & (x - 1)

我们看到,我们将 x 与 x - 1 这两个数字做按位与(这个名词的含义很简单,你随便查查资料就知道了),按位与以后的结果再赋值给 y 变量,下面我们着重来讨论 y 变量与 x 变量之间的关系。

既然是位运算,我们就需要从二进制的角度来思考这个问题。首先思考 x - 1 的二进制表示与 x 二进制表示之间的关系,当 x 二进制表示的最后一位是 1 的时候,x - 1 就相当于将 x 最后的一位 1 变成了0,如果 x 二进制表示最后一位是 0 呢,计算 x - 1 的时候,就会试图向前借位,应该是找到最近的一位不为0的位置,将这一位变成 0,原先后面的 0 都变成 1,如下图所示:

图中打 * 的部分,代表了 x 与 x - 1 二进制表示中完全相同的部分。根据按位与操作的规则,相应位置都为1,结果位就为 1,那么 x 与上 x - 1 实际效果等效于去掉 x 二进制表示中的最后一位1,从而我们发现原来 y 变量与 x 变量在二进制表示中,只差一个 1。

回到原任务,如果我们用一个数组 f 记录相应数字二进制表示中 1 的数量,那么 f[i] 就代表 i 这个数字二进制表示中 1 的数量,从而我们可以推导得到 f[i] = f[i & (i - 1)] + 1,也就是说 i 比 i & (i - 1) 这个数字的二进制表示中的 1 的数量要多一个,这样我们通过一步计算就得到 f[i] 的结果。

下面给你准备了一份参考程序:

#include <stdio.h>
int f[1001];
int main() {
    int n;
    scanf("%d", &n);
    f[0] = 0;
    for (int i = 1; i <= n; i++) {
        f[i] = f[i & (i - 1)] + 1;
    }
    for (int i = 1; i <= n; i++) {
        if (i != 1) printf(" ");
        printf("%d", f[i]);
    }
    printf("\n");
    return 0;
}

这个程序中,首先先读入一个整数 n,代表要求解的范围,然后循环 n 次,每一次通过递推公式 f[i] = f[i & (i - 1)] + 1 计算得到 f[i] 的值,最后输出 1 到 n 中每个数字二进制表示中 1 的个数。

课程小结

通过今天这个任务,你会发现,有了数组以后,我们可以记录一些计算结果,这些计算结果可能对后续的计算有帮助,从而提高程序的执行效率。关于数组的使用,会成为你日后学习中的一个重点,今天就当先热个身吧。下面呢,我来总结一下今天课程中需要你记住的重点:

  1. 使用数组,可以很方便的定义出一组变量存储空间,数组下标从 0 开始。
  2. 数据的最基本存储单位是字节,每一个字节都有一个独一无二的地址。
  3. 一个变量占用若干个字节,第一个字节的地址,是这个变量的首地址,称为:变量地址。

记住今天这些,对于日后学习指针相关知识,会有很大的帮助。好了,今天就到这里了,我是胡光,我们下期见。

精选留言

  • 学写代码的猪

    2020-01-15 21:37:33

    感觉老师课程的步子跨度还是比较大的,真的小白如果不花几倍的时间吸收消化,还真容易扯着蛋的,哈哈。
    作者回复

    d(^_^o)所以给大家推荐了三本书。资料比较多的内容,专栏里面就很少会讲,专栏里主要讲阻碍大家学习编程的部分。否则,32讲,可能才讲完语言。

    2020-01-16 22:46:49

  • 乐毅

    2020-10-15 10:45:43

    老师讲的很好,只是有个疑问,x & (x - 1)这样的表达式很简单,老师是如何知道使用这种表达式的,您的思考过程是怎样的?
  • 罗耀龙@坐忘

    2020-05-10 18:43:14

    茶艺师学编程:

    思考题:去掉倍数设计一个去掉倍数的程序,
    要求如下:
    首先读入两个数字 n 和 m,n 的大小不会超过 10,m 的大小都不会超过 10000;
    接下来读入 n 个各不相同的正整数,输出 1 到 m 中,有哪些数字无法被这 n 个正整数中任意的一个整除。

    我卡在这道题快10多天了,踩了不少的坑。

    1、没有想好核心算法
    一看到“整除”,我反射性想到“%(取模,是否等于0)”。这想法在处理单个被除数还行,但是这里要处理复数的被除数,如果接着用“&&”,程序不会算出结果。

    2、没有理解透彻“数组”
    ·我曾经试着给第二次输入的数用数组,问题是这里是在循环内这样做是没有意义,就算是输入成数组了,但我也不能拿一堆数字直接除以该数组获得结果
    ·我也试过给要输出的结果存进数组,幻想过能不能在数组里直接计算(剔除重复的、其他的可被整除数)

    3、同样的,我也没有真的理解“循环”
    看似要用“数组”,其实循环就能直接解决了·······

    交作业:

    # include <stdio.h>

    int sum[20005] = {0} ; //创建用来存放计算结果的数组sum

    int main(){
    int n, m, num, i, j; //创建变量n,m(题目的第一次输入)num(第二次输入),i,j(循环用的变量)
    printf("请输入要计算的两个数组:\n(注意:第一个数字请不要超过10,第二个请不要超过10000)\n");
    scanf( "%d%d" , &n, &m); //第一次输入
    if( n <= 10 && m <= 10000){

    for( i = 0 ; i < n ; i++ ){
    scanf( "%d" , &num ); //第二次输入,按照n的数量输入n个数字
    for ( j = num ; j <= m ; j += num ){ //核心part1:在数组sum上标记输入的整数的倍数
    sum[j] = 1; //方法为只要是在数组sum上第(输入的整数的倍数)位,对应的值为1
    }
    }
    for( i = 1 ; i <= m ; i++ ){ //核心part2:再开循环,在数组sum上一个个检查,看哪个位置的值为1
    if( sum[i] == 1 )continue; //下面是循环输出数字,continue是“跳出循环”,对应的条件就是“在XX位置上的值为1”
    printf( "%d\t" , i ); //换句话说,就是在数组sum上的值为1的对应标号i就不与输出

    }
    }
    else printf("同学,超纲啦!");
    return 0 ;
    }

    //其结果,就是通过循环(嵌套循环)、加法、数组标记、筛选(选择不输出)就到达了目的。
    作者回复

    整个思考过程很完整,很棒!尤其代码中的 j+=sum 很得循环精髓!d(^_^o)

    2020-05-13 06:12:22

  • 2020-01-16 16:23:39

    为什么scanf(“%d”,&a);是用%d整数占位符替换的&a的地址,不应该是%p吗?
    作者回复

    后面讲到指针的时候,你就明白了。&a传入的是a的地址,这样scanf才能把处理结果放到变量a里面。

    2020-01-16 19:55:30

  • Jinlee

    2020-01-15 18:13:42

    老师,您给的示例代码中,10能被5整除,为啥也输出了呢
    作者回复

    啊,sorry,笔误,多写上去的!赞!👍

    2020-01-16 22:48:13

  • 行问

    2020-01-14 07:07:55

    以二进制数字 110 为例,末尾是 0,计数量 cnt 不进入计算;然后 110 除以 2,即去掉最后一位的 0,变成了 11,此时末尾是 1,计数量 cnt 就加 1;11 再除以 2,变成了 1,此时末尾是 1,计数量 cnt 再次加 1 。最后的 n 等于 1,再除以 2,n 变成了 0,循环结束。

    这个段落的叙述、逻辑,让初学者直接挂掉了。110 除以 2,即去掉最后一位的 0, 变成了 11。我在大脑、拿纸笔,反复的推算,它都不成立。以二进制除法?十进制除法?
    作者回复

    二进制除法

    2020-01-14 07:55:29

  • 小昭

    2022-08-07 20:02:38

    这节看了好几遍哇,有时候感觉自己看明白了,可是想着想着又把自己搞懵了
  • blackcat

    2022-04-25 19:53:39

    #include<stdio.h>

    int main()
    {
    int n,m,x;
    scanf("%d %d",&n,&m);
    int a[n];
    for(int i=1;i<=n;i++)
    {
    scanf("%d",&x);
    a[i]=x;
    }
    int b[m];
    for(int j=1;j<=m;j++)
    {
    for(int i=1;i<=n;i++)
    {
    if(j%a[i]!=0)
    {
    b[j]=1;
    }
    else
    {
    b[j]=0;
    break;
    }
    }
    }
    for(int j=1;j<=m;j++)
    {
    if(b[j]==1)
    {
    printf("%d ",j);
    }
    }

    return 0;
    }
    小白一枚,最近看同余原理,尝试了一下。
  • 蓦然回首

    2020-11-27 11:09:57

    开发做的越久,越能体会到算法的重要性,数学的重要性
  • Noya

    2020-04-30 01:33:29

    // 求二进制中 1 的个数
    #include <iostream>

    using namespace std;

    int n;
    int cnt;

    int main(void)
    {

    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
    int t = i;
    while (t)
    {

    if (t & 1)
    cnt++;
    t >>= 1;
    }
    printf("%d ", cnt);
    cnt = 0;
    }
    return 0;
    }
    作者回复

    不错!基本实现了功能!d(^_^o)

    2020-05-05 14:13:35

  • 梅利奥猪猪毛丽莎肉酱

    2020-01-14 14:04:52

    嗯还是相对比较基础的,对于萌新来说建议老师在图5种补充一个y=*****000000,这样萌新会看的更加轻松,和x做对比的确就差个1,至于前面的*号为什么一致,也是因为与的运算,因为1&1=1, 0&0=0,然后在结合下面的程序看,一看就看懂了
    作者回复

    (。ì _ í。)好的,谢谢你的建议,d(^_^o)

    2020-01-15 01:45:11

  • 徐洲更

    2020-01-14 08:23:04

    如果题目求的并不是1-n的1的数,而是n的1的数量,利用递推的解决方法的时间复杂度是不是就比最开始的方法高呢
    作者回复

    对的,如果单纯的只是求n中二进制表示1的数量的话,那么直接求就好了,用不着递推。前后是两种性质完全不同的题目。

    2020-01-14 11:59:38

  • 阿阳

    2023-02-05 07:57:27

    递推公式f[i] = f[i & (i - 1)] + 1;,要求前面的先计算到,才能推后面的,不理解为啥一定能保证 f[i & (i - 1)比f[i]一定是先计算,而且是已经计算过的。
  • look for

    2022-09-11 16:43:18

    思考题:

    #include <stdio.h>

    void main()
    {
    int n, m, arr[10];
    scanf("%d", &n);
    scanf("%d", &m);

    for (int i = 0; i < n; i++)
    {
    scanf("%d", &arr[i]); // 读入n个不相同的正整数,并存入数组
    }

    for (int i = 1; i <= m; i++)
    {
    for (int j = 0; j < n; j++)
    {
    if (i % arr[j] == 0)
    {
    break;
    }
    if (j == n - 1) // 判断是否是执行到最后一个数
    {
    printf("%d\t", i);
    }
    }
    }
    }
  • 郑小菜

    2022-05-04 17:51:11

    “例如 n=8 的时候,上面循环执行 3 次” 不应该执行了4次吗?
  • blackcat

    2022-04-25 19:56:09

    看了精选留言,我还是小白了。
  • 浪里淘沙的小法师

    2021-12-05 23:19:27

    思考题,去掉倍数
    #include <stdio.h>
    int main() {
    int m, n, arr[10];
    scanf("%d%d", &n, &m);
    printf("请输入%d个数字:", n);
    for (int i = 0; i < n; i++) {
    scanf("%d", &arr[i]);
    }
    printf("以下数字不能被上面%d个数整除:",n);
    for (int i = 1; i <= m; i++) {
    int flag = 0;
    for (int j = 0; j < n; j++) {
    if (i % arr[j] == 0) {
    flag = 1;
    break;
    }
    }
    if(flag==0){
    printf("%d ",i);
    }
    }
    printf("\n");
    return 0;
    }
  • 点金

    2021-11-04 10:57:15

    这个问题的时间复杂度是O(n)的吧,不是O(1)的
  • 艾格北峰

    2021-10-13 14:43:43

    去除倍数程序:
    #include <stdio.h>

    int main() {
    int n, m;
    int c = 0;
    int num = 0;
    scanf("%d %d\n", &n, &m);
    int a[10] = {0};
    int var[1000] = {0};
    for (int i = 0; i < n; i++) {
    scanf("%d", &a[i]);
    //printf("%d ", a[i]);
    }
    for (int i = 1; i <= m; i++) {
    for (int j = 0; j < n; j++) {
    if (i % a[j] != 0) {
    c +=1;
    }
    }
    if (c == n) {
    var[num] = i;
    num += 1;
    }
    c = 0;

    }
    for (int i = 0; i < num; i++) {
    printf("%d", var[i]);
    if (i < num - 1) {
    printf(" ");
    }
    }
    printf("\n");
    return 0;

    }

  • 韩非

    2021-08-12 13:51:40

    突然悟了,y=x & (x - 1)是去掉x的最后一位的1,最后的y为x - 1,不过,那为什么不让y直接等于x -1呢,有什么坑吗🙃