【哈希、双指针】| AcWing 1532. 找硬币

题目描述

伊娃喜欢从整个宇宙中收集硬币。

有一天,她去了一家宇宙购物中心购物,结账时可以使用各种硬币付款。

但是,有一个特殊的付款要求:每张帐单,她只能使用恰好两个硬币来准确的支付消费金额。

给定她拥有的所有硬币的面额,请你帮她确定对于给定的金额,她是否可以找到两个硬币来支付。

输入格式
第一行包含两个整数 N 和 M,分别表示硬币数量以及需要支付的金额。

第二行包含 N 个整数,表示每个硬币的面额。

输出格式
输出一行,包含两个整数 V1,V2,表示所选的两个硬币的面额,使得 V1≤V2 并且 V1+V2=M。

如果答案不唯一,则输出 V1 最小的解。

如果无解,则输出 No Solution。

数据范围
1≤N≤105,
1≤M≤1000

输入样例1:

8 15
1 2 8 7 2 4 11 15

输出样例1:

4 11

输入样例2:

7 14
1 8 7 2 4 11 15

输出样例2:

No Solution

哈希

这个题目有瑕疵,没有告诉我们硬币的大小范围,只有输入硬币的个数是1e5,

  1. 如果硬币的范围远大于1e5,那么用哈希就是一个比较好的选择;
  2. 如果硬币的范围小于1e5,但我们输入的硬币个数就是1e5个,所以必然存在重复硬币,可以不用哈希,而是直接开一个1e5的数组记录个数即可,下标表示硬币,数值表示硬币的个数。

首先需要肯定,硬币的大小一定是非负数,即大于等于1。

思路
在输入时就处理数据,每输入一个数据x,如果数x大于等于m,说明一定匹配不到,可以找下一个输入数据了,否则就去查找以前已经输入过的数据,能否找到一个y,y=m-x,使其能够凑成m,如果可以的话,再去判断已经存的结果 (v1,v2)中的v1 是否小于(x,y)中的较小者,如果小于的话,就是比已知 (v1,v2) 要更接近答案的选法。

由于只选两个硬币,所以硬币存在即可,可以不用存储重复元素。

特殊情况:当m是偶数的时候,可能存在x=y=m/2的情况,但是不需要特判,因为当输入的是x=m/2时,如果是第一次输入,肯定凑不成,然后将其插入;第二次输入x=m/2的时候,在先判断之前输入的有没有y=m/2时找到了,即可凑成m,然后判断当前x是否之前已经记录过了,如果没记录过再插入,这是限制了不存在重复元素,所以里面还是只有一个m/2,但是不影响结果。
一般情况则是x和y都插入。x+y=m

但是要注意比较y有没有之前记录过插入x之间的顺序,要先比较,再插入
因为,如果先插入的话,
试想:当x=m/2时,先插入x,然后再匹配y是否已经插入过时,此时y==x,匹配到的是刚插入的x,虽然实际上只有一个x,但是却因为顺序问题,给匹配成功了。

因此要先匹配原来存在于哈希表中的数y,然后判断x是否需要插入,再把x插入。

1. 手写哈希

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=3e5+1,M=1010; "N要是素数"
int hs[N];"初始化为0,当遇到0的时候说明哈希表这个位置为空,因为硬币的价值都是大于0的"
int null=0;  "所以哈希表中的0表示null"

int find(int x)
{
    int k=x%N;
    while (hs[k] && hs[k]!=x) { "如果遇到的数hs[k]不是0并且不是不是要找的x就不停,继续解决冲突"
        k++;                    "直到遇到0,说明表中不存在x,或者遇到x,说明找到了x"
        if (k==N) k=0;
    }
    return k;  "要么h[k]为空,表示x不存在,k是插入的位置;要么h[k]=x,k是匹配的位置"
}     其实,在这个题里,这个k就是x。

int main()
{
    int n,m;
    read(n),read(m);
    int v1=M,v2; //(v1,v2)v1先初始化一个可比较且不可能的数
    int x,y;
    while (n--) {
        read(x);
        if (x>=m) continue; //必然不成立
        y=m-x;  //此时y必然大于0
        int kx=find(x),ky=find(y); //找到x和y在哈希表中存储的下标,或者是否存在于哈希表中
        if (hs[ky]) { //如果y存在的话,因为输入的是x,所以x必然存在
            if (v1>min(x,y)) v1=min(x,y),v2=max(x,y);
        }
        //接下来判断x是否需要插入,当hs[kx]==null时,说明不存在x,需要插入
        if (!hs[kx]) hs[kx]=x;
    }   "且这个插入必须放在判断if(hs[ky])后面,避免当出现x+x=m的情况时出错。否则当第一次插入x(x=m/2)时就会匹配到,会出错。"
    v1==M?puts("No Solution"):printf("%d %d",v1,v2);

    return 0;
}

2. (1)的简化写法

题目数据限制最多输入1e5个硬币,选出其中两个要凑够M,M最大值是1000。
我们不知道输入硬币的面额是多少,假设面额就是从1到1e5,设立一个bubble数组,开到1e5,下标对应面额,对应的值表示该面额有几个即可。

#include <iostream>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1e5+10,M=1010;
int bubble[N];     "记录从1到N的面值的硬币的个数"

int main()
{
    int n,m;
    read(n),read(m);
    int v1=M,v2,x,y;
    while (n--) {
        read(x);
        y=m-x;
        if(y>0 && bubble[y]) //避免数组越界,y为负数时,即相当于x>=m时不处理
            if (v1>min(x,y)) v1=min(x,y),v2=max(x,y);
        bubble[x]++;    
    }
     v1==M?puts("No Solution"):printf("%d %d",v1,v2);
    
    return 0;
}

3. unordered_set实现

#include <iostream>
#include <unordered_set>
            
using namespace std;

#define read(x) scanf("%d",&x)
const int M=1010;

int main()
{
    int n,m;
    read(n),read(m);
    int v1=M,v2,x,y;
    unordered_set<int> hash;   //set中不允许有重复元素
    while (n--) { //边读入边处理
        read(x);
        y=m-x;
        if (hash.count(y)) {//x已经必然存在
            if (v1>min(x,y)) v1=min(x,y),v2=max(x,y);
        }
        hash.insert(x);  //重复则不插入
    }
    v1==M?puts("No Solution"):printf("%d %d",v1,v2);
    
    return 0;
}

双指针

不再边输入边处理,输入完再统一处理。

#include <iostream>
#include <algorithm>
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1e5+10;

int main()
{
    int n,m;
    read(n),read(m);
    int coin[N];
    for (int i=0;i<n;i++) read(coin[i]);
    sort(coin,coin+n); //默认升序
    int i,j;
    for (i=0,j=n-1; i<j ; ) {  //左边往右走,右边往左走,相碰就可以停,相碰说明只剩下选一个硬币的方案,即没答案
        if (coin[i]+coin[j]>m) j--;   "只能用if"
        else if (coin[i]+coin[j]<m) i++;
        else break; //碰到的第一个匹配到的就可以停止,coin[i]此时最小
    }
    i==j?puts("No Solution"):printf("%d %d",coin[i],coin[j]);  "必然不会出现i>j的情况,只可能i==j退出"
    
    return 0;
}

对比二者处理的区别

	int i,j;
    for (i=0,j=n-1; i<j ; i++) {  //左边往右走,右边往左走,相碰就可以停,相碰说明只剩下选一个硬币的方案,即没答案
        while (i<j && coin[i]+coin[j]>m) j--;
        if (i<j && coin[i]+coin[j]==m) break;
    }
    i>=j?puts("No Solution"):printf("%d %d",coin[i],coin[j]);   "可能出现i>j的情况"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值