第二十一天:统计数字
每日一道C++题:统计数字
题目描述
小明前几天看书看累了,脑海中突然闪过,这书的页码也很可爱啊。一本书的页码从自然数1按自然顺序编码到n.每个页码不会含有多余的前导数字0.例如,第6页用数字6表示,而不是06、006表示。下面问题来了,你能帮忙小明解决以下问题:给定总页码n,计算出书的全部页码中分别用到的多少次数字0,1,2,…,9.
输入描述:
有多组测试用例,输入到文件末尾。每一个测试用例占一行,输入一个整数n,代表书的总页码。
1<=n<1e9
输出描述:
每10行代表一个测试用例。在一个测试用例中,在第k行输出页码中用到数字k-1的次数。(k=1,2,…,10)
#include <iostream>
#include <cstring>void countDigits(int n) {int count[10];memset(count, 0, sizeof(count));for (int i = 1; i <= n; ++i) {int num = i;while (num > 0) {count[num % 10]++;num /= 10;}}for (int i = 0; i < 10; ++i) {std::cout << count[i] << std::endl;}
}int main() {int n;while (std::cin >> n) {countDigits(n);}return 0;
}
- 定义一个长度为 10 的数组count,用于存储数字 0 到 9 出现的次数,并使用
memset
函数将其初始化为 0。 - 外层循环从 1 到n,对每一个页码进行处理。
- 对于每一个页码i,通过一个内层while循环,不断取模并更新num,统计每一位上数字出现的次数。例如,对于数字 123,先统计个位的 3,然后统计十位的 2,最后统计百位的 1。
- 除了 for 和 while 循环,还有 do - while 循环。do - while 循环的特点是先执行循环体,再判断条件,即循环体至少会执行一次。例如:
int num = 0;
do {std::cout << num << std::endl;num++;
} while (num < 3);
数组的相关知识
1. 数组的基本概念
- 定义:数组是一种数据结构,它能够存储多个相同类型的元素。这些元素在内存中是连续存储的,通过一个唯一的名称和索引(下标)来访问。例如,在 C++ 中定义一个整数数组:
int numbers[5];
,这里定义了一个名为 numbers 的数组,它可以存储 5 个 int 类型的元素。 - 用途:数组常用于批量处理数据。比如,要存储一个班级学生的成绩,就可以使用数组。
int scores[30];
可以存储 30 个学生的成绩,方便对这些成绩进行各种操作,如求平均值、找最高分等。
2.数组的声明与初始化 - 声明:声明数组时,需要指定数组的类型、名称和大小。例如:
float prices[10];
:声明了一个可以存储 10 个 float 类型元素的数组 prices。数组大小必须是一个常量表达式,在编译时就能确定。 - 初始化:
- 完全初始化:在声明数组的同时为每个元素提供初始值。例如
int nums[3] = {1, 2, 3};
,这里数组 nums 的三个元素分别被初始化为 1、2、3。 - 部分初始化:只初始化部分元素,其余元素会根据数组类型自动初始化。对于数值类型,未初始化的元素会被初始化为 0;对于字符数组,未初始化的元素会被初始化为空字符 ‘\0’。例如
int arr[5] = {1, 2};
,数组 arr 的前两个元素被初始化为 1 和 2,后三个元素自动初始化为 0。 - 省略大小初始化:可以在初始化时省略数组大小,编译器会根据初始化列表中的元素个数自动确定数组大小。例如
int values[] = {4, 5, 6};
,这里数组 values 的大小为 3。
3.数组元素的访问 - 数组元素通过索引(下标)来访问,索引从 0 开始。例如,对于数组 int numbers[5];,可以通过 numbers[0] 访问第一个元素,numbers[1] 访问第二个元素,以此类推,直到 numbers[4] 访问第五个元素。
- 访问数组元素时,要确保索引在有效范围内(0 到数组大小 - 1)。如果访问越界,会导致未定义行为,这可能会引起程序崩溃、数据损坏等问题。例如,对于 int numbers[5];,如果使用 numbers[5] 访问元素,就是越界访问,因为有效索引范围是 0 到 4。
4.数组的内存布局 - 数组在内存中是连续存储的。假设 int numbers[3];,并且数组 numbers 的起始地址为 0x1000(这只是一个假设的地址),由于 int 类型在常见系统中通常占 4 个字节,那么 numbers[0] 存储在地址 0x1000,numbers[1] 存储在 0x1004(0x1000 + 4),numbers[2] 存储在 0x1008(0x1000 + 2 * 4)。这种连续的内存布局使得数组在遍历和访问时效率较高,因为可以通过简单的地址偏移来快速定位到每个元素。
5.数组的遍历 - 通常使用循环来遍历数组,以便对每个元素进行操作。例如,使用 for 循环遍历并输出
int numbers[5] = {1, 2, 3, 4, 5};
数组的元素:
for (int i = 0; i < 5; i++) {std::cout << numbers[i] << " ";
}
- 也可以使用 while 循环或 do - while 循环来遍历数组,只是 for 循环在处理这种已知循环次数的情况时更为常用和直观。
6.多维数组 - 多维数组是数组的数组。以二维数组为例,它可以看作是一个表格形式的数据结构。例如,定义一个二维整数数组
int matrix[3][4]
;,它可以看作是一个有 3 行 4 列的矩阵。 - 二维数组的初始化可以有多种方式,例如:
int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}
};
- 访问二维数组元素需要两个索引,第一个索引表示行,第二个索引表示列。例如,matrix[1][2] 表示访问第 2 行第 3 列的元素(索引从 0 开始),在上述例子中这个元素的值为 7。
- 多维数组在处理一些需要二维或更高维结构的数据时非常有用,比如图像处理中可以用二维数组表示图像的像素矩阵。
7.数组作为函数参数 - 当数组作为函数参数传递时,实际上传递的是数组首元素的地址,数组的大小信息会丢失。例如:
void printArray(int arr[], int size) {for (int i = 0; i < size; i++) {std::cout << arr[i] << " ";}
}
int main() {int numbers[5] = {1, 2, 3, 4, 5};printArray(numbers, 5);return 0;
}
- 在 printArray 函数中,虽然参数写成 int arr[],但实际上 arr 是一个指针,指向数组 numbers 的首元素。因此,需要额外传递数组的大小参数 size,以便在函数内部正确处理数组。
8.与数组相关的 C++ 标准库容器 std::vector:
它是 C++ 标准库提供的动态数组,与普通数组相比,它能自动管理内存,在运行时可以改变大小。例如:
```cpp
#include <vector>
#include <iostream>
int main() {std::vector<int> vec;vec.push_back(1);vec.push_back(2);for (size_t i = 0; i < vec.size(); i++) {std::cout << vec[i] << " ";}return 0;
}
std::array:
它是 C++11 引入的一个固定大小的数组容器,与普通数组类似,但提供了一些额外的功能和安全性。例如:
#include <array>
#include <iostream>
int main() {std::array<int, 3> arr = {1, 2, 3};for (size_t i = 0; i < arr.size(); i++) {std::cout << arr[i] << " ";}return 0;
}
std::array
有一些成员函数,如 size() 可以获取数组的大小,与普通数组相比,它在使用上更安全和方便。