洛谷P15799 [GESP202603 五级] 找数 题解

题目

题目传送门

题目背景

对应的选择、判断题:试题 - GESP 202603 C++ 五级 - 洛谷有题

题目描述

给定一个包含 n 个互不相同的正整数的数组 A 与一个包含 m 个互不相同的正整数的数组 B,请你帮忙计算有多少个数在数组 A 与数组 B 中均出现。

输入格式

第一行包含两个整数 n,m。

第二行包含 n 个正整数 a1​,a2​,⋯,an​ 表示数组 A。

第三行包含 m 个正整数 b1​,b2​,⋯,bm​ 表示数组 B。

输出格式

输出一个整数,表示在数组 A 与数组 B 中均出现的数的个数。

输入输出样例

输入 #1

复制代码
3 5
4 2 3
3 1 5 4 6

输出 #1

复制代码
2

说明/提示

样例解释

样例 1 中,4、3 在数组 A 与 B 中均出现。

数据范围

对于 40% 的数据,保证 1≤n,m≤1000。

对于 100% 的数据,保证 1≤n,m≤,1≤​,​≤

解题时间

相信大家第一反应都是用二重循环,但是1e9的数据告诉你这样不行,所以我们得换一种思路,例如取尺法或离散化。

暴力法

当然,对于暴力的方法,还是能拿40分的,由于思路比较简单,这里直接上代码:

cpp 复制代码
//超时
#include<bits/stdc++.h>
using namespace std;
int n,m;
int main(){
    cin>>n>>m;
    int a[n+5],b[m+5];
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=m;i++){
        cin>>b[i];
    }
    long long ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(a[i]==b[j]) ans++;
        }
    }
    cout<<ans;
    return 0;
}

离散化

接下来是离散化的代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,m;
    cin>>n>>m;
    vector<int> A(n),B(m);
    vector<int> all;
    for(int i=0;i<n;i++){
        cin>>A[i];
        all.push_back(A[i]);
    }
    for(int i=0;i<m;i++){
        cin>>B[i];
        all.push_back(B[i]);
    }
    sort(all.begin(),all.end());
    all.erase(unique(all.begin(),all.end()),all.end());
    auto get_id=[&](int val)->int{
        return lower_bound(all.begin(),all.end(),val)-all.begin();
    };
    vector<bool> mark(all.size(),false);
    for(int x:A){
        mark[get_id(x)]=true;
    }
    int ans=0;
    for(int x:B){
        if(mark[get_id(x)]){
            ans++;
        }
    }
    cout<<ans;
    return 0;
}
  1. all容器:收集了两个数组中所有的元素,这是离散化的原料。

  2. 排序去重sortunique+erase是离散化的标准写法,去重后每个数值对应唯一的离散化编号。

  3. get_idlambda :通过二分查找lower_bound找到原值在all中的下标,这就是离散化后的编号。时间复杂度O(log⁡K)O(logK),其中KK是不同元素个数。

  4. vector<bool>标记:离散化后下标范围变小了(最多n+mn+m个不同值),可以直接用数组下标标记,省去了哈希表的开销。

set神器

其实还有更简单的解法,并且实现难度不逊于离散化和暴力,先上代码:

cpp 复制代码
//用set函数
#include<bits/stdc++.h>
using namespace std;
int n,m;
set<int> s;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        s.insert(x);
    }
    long long ans=0;
    for(int i=1;i<=m;i++){
        int y;
        cin>>y;
        s.count(y)?ans++:ans+=0;
    }
    cout<<ans;
    return 0;
}

这段代码巧妙运用到了set函数,用s.insert(x)将x存入容器s中,然后用s.count(y)来判断容器s中是否有重复的数,如果有ans就加1,否则不操作(用三目运算符更简便,还能节省打代码的时间)。

取尺法

取尺法的核心思想是:将两个数组排序后,用两个指针分别扫描,比较当前元素的大小关系

具体做法:

  1. 将数组A和数组B分别从小到大排序

  2. 初始化两个指针i=0(指向A开头),j=0j(指向B开头)

  3. 当i<n且j<m时循环:

    • 若Ai<Bj,说明Ai太小,i右移

    • 若Ai>Bj,说明Bj太小,j右移

    • 若Ai==Bj,说明找到公共元素,答案加一,同时i和j都右移(跳过这个相等的数,避免重复统计)

这种方法的优势是:

  • 不需要额外的哈希表或离散化映射

  • 只需要O(1)的额外空间(除了存储原数组)

  • 排序后一次线性扫描即可完成

代码:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,m;
    cin>>n>>m;
    vector<int> A(n),B(m);
    for(int i=0;i<n;i++) cin>>A[i];
    for(int i=0;i<m;i++) cin>>B[i];
    sort(A.begin(),A.end());
    sort(B.begin(),B.end());
    int i=0,j=0,ans=0;
    while(i<n&&j<m){
        if(A[i]<B[j]) i++;
        else if(A[i]>B[j]) j++;
        else{
            ans++;
            i++;
            j++;
        }
    }
    cout<<ans;
    return 0;
}

算法图解

假设A=3,1,4,2,B=5,2,3,1

排序后:

  • A=1,2,3,4

  • B=1,2,3,5

双指针扫描过程:

步骤 i j Ai Bj 比较 操作 ans
1 0 0 1 1 相等 ans++,i++,j++ 1
2 1 1 2 2 相等 ans++,i++,j++ 2
3 2 2 3 3 相等 ans++,i++,j++ 3
4 3 3 4 5 4<5 i++ 3
5 4 3 - 5 i==n退出 - 3

最终答案:3(公共元素为1,2,3)

正确性证明

引理:排序不会改变两个数组中公共元素的集合。

证明:排序只是重新排列元素顺序,元素本身没有变化,所以A和B中共同拥有的数值集合保持不变。

算法正确性

  • 当Ai<Bj时,由于两个数组都是升序的,Ai不可能与B中j及j之后的任何元素相等(因为Bj已经是最小的未比较元素且比Ai大),所以Ai可以安全跳过

  • 当Ai>Bj时同理,Bj可以安全跳过

  • 当Ai==Bj时,找到了一个公共元素,将两个指针同时后移,避免重复计数

由于每次循环至少移动一个指针,最多移动n+m次,所以算法必然在有限步内结束并得到正确结果。

总结

本题的算法实现非常多,但是思路各有不同。我个人建议在考场上可以用set和取尺法,时间紧迫时可以用暴力骗点分,如果在家里写代码的话可以尝试用离散化突破自我,如果你有更好的解题方案,可以告诉我,本期就到这里,掰掰?

相关推荐
bIo7lyA8v2 小时前
算法中的随机化思想及其复杂度收益评估的技术8
算法
数据法师2 小时前
视频文件重复检测工具:基于哈希与视频指纹的三级筛选机制
算法·音视频·哈希算法
其实防守也摸鱼2 小时前
软件安全与漏洞--Windows底层原理与软件逆向工程基础
linux·网络·数据库·算法·安全·安全架构·软件安全与漏洞
bIo7lyA8v2 小时前
算法稳定性与数据分布的内在联系研究的技术8
算法
SHARK_pssm3 小时前
【数据结构——树与堆】
c语言·数据结构·经验分享·笔记
blueman88883 小时前
VS2022 切换定义(F12 / Go to Definition)反应慢
c++·visual studio
bIo7lyA8v3 小时前
算法可视化对教学与调试效率的影响分析的技术8
算法
凡人叶枫3 小时前
Effective C++ 条款35:考虑 virtual 函数以外的其他选择
java·c++·spring
郝学胜-神的一滴3 小时前
CMake 017:彩色日志输出实战
linux·c语言·开发语言·c++·软件工程·软件构建·cmake
hunterkkk(c++)3 小时前
优先队列启发式最短路径快速算法(优化SPFA)-HEAP_SPFA算法
算法