Smallest Difference(最小差值)三种角度求解

来源:互联网 发布:linux 文件引用计数 编辑:程序博客网 时间:2024/05/19 21:43

  • 前言
  • 题目
  • 分析
    • 一调用全排列函数
    • 二手写全排列DFS
    • 三贪心找最佳方案
      • 如果n为奇数
      • 如果n为偶数
  • 总结

前言

最近作者做到了一道题,赶脚这道题十分的beautiful(ugly),老师让我们让三种不同的方法做出来,我苦苦冥想….,最终三种方法都AC了,心里美滋滋~~。How happy I am!(How happy I am!)

题目

题目描述
给定若干位十进制数,你可以通过选择一个非空子集并以某种顺序构建一个数。剩余元素可以用相同规则构建第二个数。除非构造的数恰好为0,否则不能以0打头。
举例来说,给定数字0,1,2,4,6与7,你可以写出10和2467。当然写法多样:210和764,204和176,等等。最后一对数差的绝对值为28,实际上没有其他对拥有更小的差。
Input - 输入
输入第一行的数表示随后测试用例的数量。
对于每组测试用例,有一行至少两个不超过10的十进制数字。(十进制数字为0,1,…,9)每行输入中均无重复的数字。数字为升序给出,相隔恰好一个空格。
Output - 输出
对于每组测试用例,输出一个以上述规则可获得的最小的差的绝对值在一行。
Sample Input - 输入样例
1
0 1 2 4 6 7
Sample Output - 输出样例
28
附上题目链接:Smallest Difference(最小差值)


分析

这道题想必大家也看到了,输入便很恶心,作者一来就将读入优化改了改就可以解决,什么!你不知道读入优化,那先看看作者好基友武ZY的一篇关于读入优化和输出优化的博客吧:读入优化&输出优化
输入处理代码如下:

        int n=0,num[11];        char c;        while(1)        {            scanf("%c",&c);            if(c==' ')continue;            if(c=='\n')break;            num[++n]=c-'0';        }

好了,接下来进入正文了,你如何将一串有序而且元素个数还不大于10的数列重新组合拼成两个数并求所有组合出两数的最小差值??说到这里,你肯定想到了爆搜!作者也想到了……但作者没写,貌似不能过。

我们看到这道题有没有想到将这些数进行全排列?然后从中间剪开拼成两个数??没错,这就是正解!我们在读入的时候将这串数长度存为n,同时思考的时候就要想到要让两数差最小就要将他们位数尽量相同,也就是,它们位数尽量接近,不妨让a为较大数,b为较小数,那么就进行分类讨论:当n为偶数时令a位数为n/2,b位数也为n/2,当n为偶数时令a位数为n/2+1,b位数为n/2,如图所示:

Created with Raphaël 2.1.0判断nn为奇数a=n/2+1b=n/2n为偶数a=n/2b=n/2yesno

好了,接下来就有三种不同的方法:

一、调用全排列函数。

相信很多人第一眼看到这一题就想到了算法函数库(algorithm)中的next_permutation()函数,它的函数作用是将数组中从第i个数至第j个数进行全排列。对于这道题正好,你将它排列出的数进行分割,还要让首位不为 0,枚举所有的数后相减取其中最小值就可以了。这是最好写的一种。代码如下:

#include<deque>#include<cstdio>#include<vector>#include<cstring>#include<algorithm>#define INF int(1e9)//1e9=1000000000using namespace std;int read()//请忽视(这是读入优化){    int f=1,x=0;    char c=getchar();    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}    return f*x;}int num[11];//这是用来存读入的数字的int main(){    char c;    int t,n=0,ans,tot=0,a,b;    scanf("%d",&t);    while(t!=0)    {        n=0;        ans=INF;//注意存答案的变量要赋极大值        while(1)//这一段就是前面的'伪'读入优化        {            scanf("%c",&c);            if(c==' ')continue;            if(c=='\n')break;            num[++n]=c-'0';        }        if(n==2)//自己写的next_permutation貌似不能处理长度为二的数列,于是特判        {            t--;            printf("%d\n",num[2]-num[1]);            continue;        }        while(next_permutation(num+1,num+n+1))//进行这些数字的全排列        {            if(num[1]!=0&&num[n/2+1]!=0)//判断数字首位不能为0            {                a=b=0;                for(int i=1;i<=n/2;i++)//分割数                    a=a*10+num[i];                  for(int j=n/2+1;j<=n;j++)//分割数                    b=b*10+num[j];                tot=a-b;                if(tot<0) tot=-tot;//取绝对值                if(tot<ans) ans=tot;//给存答案的变量更新            }        }        printf("%d\n",ans);        t--;    }    return 0;}

二、手写全排列(DFS)。

我们如何手写全排列?如果你用简单的搜索来做肯定要超时,如果你的代码和下面的差不多,那你这题就TLE(超时)了:

#include<deque>#include<cstdio>#include<vector>#include<cstring>#include<algorithm>using namespace std;int n,a[11];bool flag[11];void DFS(int x)//枚举当前第几位{    if(x>n) //输出方案    {        printf("%d",a[1]);        for(int i=2;i<=n;i++)            printf(" %d",a[i]);        printf("\n");        return;     }    for(int i=1;i<=n;i++)        if(flag[i]==0)//若这i个数没被用过        {            a[x]=i;            flag[i]=1;//标记            DFS(x+1);               flag[i]=0;//标记清零        }    return ;}int main()  {      scanf("%d",&n);    DFS(1);    return 0;  }  

所以我们要优化!!!我们可以用一种类似于选择排序的思想,交换第i个数和第j个数,然后进入递归再交换,代码如下:

#include<deque>#include<cstdio>#include<vector>#include<cstring>#include<algorithm>#define INF int(1e9)//1e9=1000000000int read(){    int f=1,x=0;    char c=getchar();    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}    return f*x;}int num[11],ans,n;void dfs(int i){    if(i>n)    {        if(num[1]!=0&&num[n/2+1]!=0)//仍然判断数字首位不为零        {            int a=0,b=0,tot;            for(int i=1;i<=n/2;i++)                a=a*10+num[i];              for(int j=n/2+1;j<=n;j++)                b=b*10+num[j];            tot=a-b;            if(tot<0) tot=-tot;//取绝对值            if(tot<ans) ans=tot;        }        return ;    }       for(int j=i;j<=n;j++)    {        swap(num[i],num[j]);//交换两数再进行判断        dfs(i+1);        swap(num[i],num[j]);//交换回来    }    return ;}int main(){    int t=read();    char c;    while(t!=0)    {        n=0;        ans=INF;        while(1)        {            scanf("%c",&c);            if(c==' ')continue;            if(c=='\n')break;            num[++n]=c-'0';        }        if(n==2)        {            t--;            printf("%d\n",num[2]-num[1]);            continue;        }        dfs(1);        printf("%d\n",ans);        t--;    }    return 0;}

三、贪心找最佳方案。

我们还可以通过贪心找最佳方案。

1.如果n为奇数。

这说明之前所设的a必定为较大数,那我们就要让大的数尽量小,小的数尽量大,才能使他们差值最小。于是我们要把最小的一个非零数字给a,如果有零,那作为a的第二位,然后将所剩的数从小到大填入a,直到a的位数达到n/2+1,然后再将剩下的数从大到小给b,那么这样a,b两数差之必然最小。

2.如果n为偶数。

还是设a为较大数,两个位数相等的数他们首位越接近,差就越小于是我们要把所有首位非零的数枚举一遍,找出差值最小的多组数分别作为a,b的首位。还是将较大的数给a,较小的数给b,然后处理方法就和之前的处理方法一样,还是大的数尽量小,小的数尽量大,才能使他们差值最小。如果有零,那作为a的第二位,然后将所剩的数从小到大填入a,直到a的位数达到n/2+1,然后再将剩下的数从大到小给b,那么这样a,b两数差之必然最小(我不会告诉你我写的后面我是复制前面的~~),代码如下:

#include<cstdio>#include<cstring>#include<algorithm>#include<vector>#include<deque>#define INF int(1e9)using namespace std;int read(){    int f=1,x=0;    char c=getchar();    while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}    while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();}    return f*x;}int num[11],ans,n,minvalue[11];//minvalue存的有最小差值为mintot的相邻两数int main(){    int t=read(),a,b,mintot,mint;//有最小差值为mintot的相邻两数有mint对    char c;    while(t!=0)    {        mintot=10;//mintot最多也就等于8对        mint=a=b=n=0;//a较大数b较小数        ans=INF;        while(1)        {            scanf("%c",&c);            if(c==' ')continue;            if(c=='\n')break;            num[++n]=c-'0';        }        if(n==2)        {            t--;            printf("%d\n",num[2]-num[1]);            continue;        }        else if(n%2!=0)//如果n为奇数,对照前面情况1        {            if(num[1]==0)//处理首位为零的情况            {                a=num[2]*10;                for(int i=3;i<=(n+1)/2;i++)                    a=a*10+num[i];            }            else//首位不为零            {                for(int i=1;i<=(n+1)/2;i++)                    a=a*10+num[i];            }            for(int i=n;i>=(n+1)/2+1;i--)                b=b*10+num[i];            ans=a-b;        }        else //如果n为偶数,对照前面情况2        {            for(int i=2;i<=n;i++)//找最小差值为mintot的相邻两数有几对            {                if(num[i]-num[i-1]<mintot&&num[i-1]!=0)//一定要首位不为零                {                    mintot=num[i]-num[i-1];                    mint=1;                    minvalue[mint]=i;                }                else if(num[i]-num[i-1]==mintot&&num[i-1]!=0)                    minvalue[++mint]=i;            }            for(int i=1;i<=mint;i++)//分别尝试这最小差值为mintot的相邻两数            {                a=num[minvalue[i]];                 b=num[minvalue[i]-1];                int j=1,t=0;                while(t<n/2-1)                {                    if(j!=minvalue[i]&&j!=minvalue[i]-1)                    {                        a=a*10+num[j];                        t++;                        }                    j++;                }                j=n,t=0;                while(t<n/2-1)                {                    if(j!=minvalue[i]&&j!=minvalue[i]-1)                    {                        b=b*10+num[j];                        t++;                        }                    j--;                }                if(a-b<ans)                    ans=a-b;            }        }        printf("%d\n",ans);        t--;    }    return 0;}

总结

这三种方法各有各的好处,但我们看看三种方法性能的对比(手打的我知道很丑..):
| 方法——当题状况————耗时——内存—代码长度 |
| 一方法一 |
|———————————————————————|
| 二 方法二|
|———————————————————————|
| 三 方法三 |
|———————————————————————|
所以这道题贪心是最好的!!!

原创粉丝点击