489
社区成员
给定一个含n个整数的数组A,请找到数组中从未出现过的最小正整数
第一行输入$n(1≤n≤10^6)$,第二行输入空格分隔的n个整数$A_i(−10^{18}≤Ai≤10^{18})$
输出一个整数:数组中从未出现过的最小正整数
4
-5 3 2 3
在这里给出相应的输出。例如:
1
代码长度限制: 16 KB
时间限制: 400 ms
内存限制: 64 MB
这是一道C++题目。
此题要求找到一个给定整数数组中从未出现过的最小正整数。即给定一个整数数组,我们应该返回数组中不存在的最小正整数。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
long long n = 0;
cin >> n;
//最小整数最大不超过 n+1
vector<long long> a(n+1);
for (long long i = 1; i <= n; i++)
{
cin >> a[i];
if (a[i] < 1 || a[i] > n)
{
a[i] = n+1;
}
}
for (long long i = 1; i <= n; i++)
{
long long val = abs(a[i]);
// val 是数组中的数据
if (val <= n && a[val] > 0) // a[]的数据如果已经被标记则不需要
{
a[val] = -a[val];
// 把a[i] 放到 a[i] 之中 a[a[i]]
}
}
for (long long i = 1; i <= n; i++)
{//从1开始寻找,找到第一个a[i] != i的数
//前面使用负数进行标记,所以使用a[i]>0
if (a[i] > 0)
{
cout << i << endl;
return 0;
}
}
cout << n+1 << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
long long n;
cin >> n;
vector<long long> a(n + 1);
for (long long i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] < 1 || a[i] > n) { // 忽略所有负数和大于 n 的数
a[i] = n + 1;
}
}
for (long long i = 1; i <= n; i++) {
long long val = abs(a[i]);
if (val <= n) { // 对于每个在 1 到 n 范围内的数 a[i]
if (a[val] > 0) a[val] = -a[val]; // 把它放到 a[a[i]] 的位置上
}
}
for (long long i = 1; i <= n; i++) {
if (a[i] > 0) { // 找出第一个 a[i] != i 的位置 i
cout << i << endl;
return 0;
}
}
cout << n + 1 << endl; // 如果所有数都在其应在的位置上,输出 n+1
return 0;
}
4 -5 3 2 3
输出 1
5 1 2 3 4 5
输出 6
5 0 -1 -2 -3 -4
输出 1
代码基于 "原地哈希(In-place Hashing)" 的技术。
开始的几行是标准的 C++ 程序开头,包括引入所需的库和启动主函数。
#include <bits/stdc++.h> // 这是什么?
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
// 关键点
其中ios...
这句代码有效提升计算效率[[3_小H的最小正整数#I/O优化 | IO优化]]
#include <bits/stdc++.h>
[[3_小H的最小正整数#非标准头文件_<bits/stdc++.h> | 这个是什么头文件?]]
接下来我们声明一个长整型变量 n
,并从标准输入读取 n
的值。
long long n;
cin >> n;
我们创建了一个大小为 n + 1
的向量 a
,并从标准输入读取 n
个整数到 a
中。如果读取的数小于 1
或大于 n
,我们把它设为 n+1
,因为这些数对于我们要找的最小正整数是没有用的。
vector<long long> a(n + 1);
for (long long i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] < 1 || a[i] > n) { // 忽略所有负数和大于 n 的数
a[i] = n + 1;
}
}
接下来我们遍历数组,对于每个在 1
到 n
范围内的数 a[i]
,我们把它放到 a[a[i]]
的位置上(注意,这可能需要多次交换)。这个过程的目的是尽可能地让数组的第 i
个位置上的数为 i
。为了标记元素 i
是否出现过,我们将 a[i]
取负。
for (long long i = 1; i <= n; i++) {
long long val = abs(a[i]);
if (val <= n) { // 对于每个在 1 到 n 范围内的数 a[i]
if (a[val] > 0) a[val] = -a[val]; // 把它放到 a[a[i]] 的位置上
}
}
接下来的循环是检查我们的操作是否成功。我们遍历 a
,如果找到第一个 a[i]
不等于 i
的位置,那么 i
就是我们要找的最小的未出现过的正整数,我们打印 i
并结束程序。
为什么要使用
abs(a[i])
呢?
a[a[i]]
就等于 -a[i]
表示这个数字已存在,等到第2个数据,因为a[i]
被提前取负了,为了还原原本的数据,所以需要abs(a[i])
for (long long i = 1; i <= n; i++) {
if (a[i] > 0) { // 找出第一个 a[i] != i 的位置 i
cout << i << endl;
return 0;
}
}
如果我们没有在上一步结束程序,那么说明 a
中已经包含了 1
到 n
的所有数,所以我们要找的最小的未出现过的正整数就是 n+1
,我们打印 n+1
并结束程序。
cout << n + 1 << endl; // 如果所有数都在其应在的位置上,输出 n+1
return 0;
}
这个算法的时间复杂度是 $O(n)$,符合题目的要求。
ios_base::sync_with_stdio(false)
和cin.tie(NULL)
是C++中的两个I/O
优化设置:
ios_base::sync_with_stdio(false)
用于关闭C++和C的IO同步。默认情况下,C++的cin和cout与C的stdio流是同步的,这样有时会降低C++的IO速度。设置为false可以关闭同步,提高C++的IO速度。cin.tie(NULL)
用于将cin流绑定到标准输出stdout
。默认情况下,cin
是绑定到stderr
的,这样每次读取输入时都要刷新stderr
,影响效率。cin.tie(NULL)
将cin
绑定到stdout
,可以避免每次刷新stderr
。结合这两个设置,可以显著提高C++的IO效率。
常见的用法是在程序开始时添加:
ios_base::sync_with_stdio(false);
cin.tie(NULL);
这可以优化C++程序的IO速度。
ios_base:
sync_with_stdio:
false:
cin:
tie:
NULL:
ios_base::sync_with_stdio(false)
关闭C++和C IO同步cin.tie(NULL)
解除cin绑定,避免刷新输出流#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
scanf("%d", &n);
int *a = (int *)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
int *b = (int *)calloc(n + 1, sizeof(int));
for (int i = 0; i < n; i++) {
if (a[i] > 0 && a[i] <= n) {
b[a[i]] = 1;
}
}
for (int i = 1; i <= n; i++) {
if (b[i] == 0) {
printf("%d", i);
return 0;
}
}
printf("%d", n + 1);
return 0;
}
这是另一个解决相同问题的 C 代码。它相比于之前的 C++ 代码更加简单直接。代码的基本逻辑是使用额外的数组 b
来标记数组 a
中出现过的正整数。这种方法的时间复杂度也是 $O(n)$,但它的空间复杂度也是 $O(n)$,比原地哈希的方法空间复杂度高。
下面是代码的逐行解释:
#include <stdio.h>
#include <stdlib.h>
int main() {
上面这几行是标准的 C 程序开头,包括引入所需的库和启动主函数。
int n;
scanf("%d", &n);
我们声明一个整数 n
,并从标准输入读取 n
的值。
int *a = (int *)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
我们创建一个大小为 n
的动态数组 a
,并从标准输入读取 n
个整数到 a
中。
int *b = (int *)calloc(n + 1, sizeof(int));
我们创建一个大小为 n + 1
的动态数组 b
,并初始化所有元素为 0。数组 b
用于标记哪些数字已经在 a
中出现过。
for (int i = 0; i < n; i++) {
if (a[i] > 0 && a[i] <= n) {
b[a[i]] = 1;
}
}
我们遍历 a
,对于每个在 1
到 n
范围内的数 a[i]
,我们在 b
中对应的位置标记为 1
。
for (int i = 1; i <= n; i++) {
if (b[i] == 0) {
printf("%d", i);
return 0;
}
}
我们遍历 b
,找出第一个 b[i]
等于 0
的位置 i
,那么 i
就是数组中从未出现过的最小正整数,我们打印 i
并结束程序。
printf("%d", n + 1);
return 0;
}
如果我们没有在上一步结束程序,那么说明 a
中已经包含了 1
到 n
的所有数,所以我们要找的最小的未出现过的正整数就是 n+1
,我们打印 n+1
并结束程序。
总的来说,这个 C 代码在思路上更直观,但是需要额外的空间来存储 b
。而之前的 C++ 代码使用 "原地哈希" 的技巧,避免了额外的空间使用,但是代码相对复杂一些。
#include <bits/stdc++.h>
不是一个标准的C++头文件,而是一些C++库实现中定义的一个非标准头文件,包含了C++标准库的大多数头文件。
bits/stdc++.h这个头文件通常包含: