小H的最小正整数_PTA_C++

TiOzolo 2023-11-18 23:30:12

小H的最小正整数_PTA_C++

给定一个含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++题目。

1. 题目理解与分析

1.1 题目简述

此题要求找到一个给定整数数组中从未出现过的最小正整数。即给定一个整数数组,我们应该返回数组中不存在的最小正整数。

1.2 可能的困难与注意事项

  • 注意,我们在寻找的是正整数,不包括0。
  • 这题需要对数组进行操作,需要考虑时间复杂度和空间复杂度,特别是当数组的大小达到$10^6$时。

    2. 解题思路与算法设计

    2.1 基础解题思路

    基本的解题思路是:
  1. 遍历数组,找到所有的正整数并将它们放在一个集合中。
  2. 从1开始递增,查找这个数是否在集合中,如果不在则这就是我们要找的数。

    2.2 优化解题思路

    优化的解题思路是:
  3. 首先,我们可以忽略所有的负数和零,因为我们在寻找的是正整数。
  4. 其次,对于大于n的正整数,我们也可以忽略,因为我们在寻找的是未在数组中出现过的最小正整数,这个数不可能大于n。
  5. 使用原地哈希的方式,将每个正整数放到其对应的索引位置,例如,数5应该在索引4的位置。
  6. 再次遍历数组,找到第一个位置上的数和位置不对应的索引,返回索引+1,就是我们要找的最小正整数。

    3. 代码实现与测试

    3.1 代码方案

    3.1.1 代码1

#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;

}

3.1.2 优化解法

#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;
}

3.2 测试数据

  • 测试数据1:4 -5 3 2 3 输出 1
  • 测试数据2:5 1 2 3 4 5 输出 6
  • 测试数据3:5 0 -1 -2 -3 -4 输出 1

    4. 知识点总结

  • 理解哈希表的使用和实现方式
  • 理解如何通过原地哈希将空间复杂度降为O(1)
  • 理解如何通过元素交换实现原地哈希

    5. 问题拓展与延伸

    如果要求找到所有未在数组中出现过的正整数,你会如何解决?

    6. 总结

    本题是一道关于数组和哈希表应用的问题,通过理解和使用哈希表,我们可以有效地解决这个问题。优化解法使用了原地哈希的思想,避免了额外的空间使用# 试题解析文档
    这是一道C++题目。

逐行代码解释

代码基于 "原地哈希(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;
        }
    }

接下来我们遍历数组,对于每个在 1n 范围内的数 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])呢?

  • 本题使用了负数来作为flag进行标记,如果输入的第1个数据存在与n的范围内,那么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 中已经包含了 1n 的所有数,所以我们要找的最小的未出现过的正整数就是 n+1,我们打印 n+1 并结束程序。

    cout << n + 1 << endl; // 如果所有数都在其应在的位置上,输出 n+1
    return 0;
}

这个算法的时间复杂度是 $O(n)$,符合题目的要求。


I/O优化

ios_base::sync_with_stdio(false)cin.tie(NULL)是C++中的两个I/O优化设置:

  1. ios_base::sync_with_stdio(false) 用于关闭C++和C的IO同步。默认情况下,C++的cin和cout与C的stdio流是同步的,这样有时会降低C++的IO速度。设置为false可以关闭同步,提高C++的IO速度。
  2. 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:

  • ios_base是C++中所有IO流对象的基类,提供了一些全局的流状态标志。
    sync_with_stdio:
  • 是一个ios_base的静态函数,用来设置C++的cin/cout流与C的stdio流是否同步。
    false:
  • 是传递给sync_with_stdio函数的参数,设置为false表示关闭同步,不再让C++的IO流同步于stdio。
    cin:
  • cin对象代表标准输入流。
    tie:
  • tie()是一个IO流对象的成员函数,用于绑定两个流对象。
    NULL:
  • 这里表示将参数设为空,即解除cin对象当前的绑定。
    总结一下:
    ios_base::sync_with_stdio(false) 关闭C++和C IO同步
    cin.tie(NULL) 解除cin绑定,避免刷新输出流
    组合使用可以优化C++的IO速度。

其他方案

#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,对于每个在 1n 范围内的数 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 中已经包含了 1n 的所有数,所以我们要找的最小的未出现过的正整数就是 n+1,我们打印 n+1 并结束程序。
总的来说,这个 C 代码在思路上更直观,但是需要额外的空间来存储 b。而之前的 C++ 代码使用 "原地哈希" 的技巧,避免了额外的空间使用,但是代码相对复杂一些。


非标准头文件_<bits/stdc++.h>

#include <bits/stdc++.h> 不是一个标准的C++头文件,而是一些C++库实现中定义的一个非标准头文件,包含了C++标准库的大多数头文件。
bits/stdc++.h这个头文件通常包含:

  • algorithm - 算法头文件
  • utility - 工具头文件
  • string - 字符串头文件
  • vector - 向量头文件
  • iostream - IO流头文件
  • iomanip - IO操作头文件
  • cmath - 数学头文件
  • and more...
    也就是说,包含bits/stdc++.h一个头文件,就相当于包含了整个C++标准库。
    但是,bits/stdc++.h作为一个非标准头文件,在不同的编译器和标准库实现中,其具体包含的内容可能有所不同。并且直接包含这个头文件也会导致编译依赖整个C++标准库。
    所以通常不推荐直接包含bits/stdc++.h头文件,而是应该只包含实际需要的标准头文件,如, 等。直接包含bits/stdc++.h会导致编译时间的增加和不可移植性。
...全文
72 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

489

社区成员

发帖
与我相关
我的任务
社区描述
闽江学院IT领域专业的学生社区
社区管理员
  • c_university_1157
  • 枫_0329
  • 傅宣
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧