您的位置:首页 > Web前端

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

2017-11-23 14:07 190 查看
前言

题目

分析
一调用全排列函数

二手写全排列DFS

三贪心找最佳方案
如果n为奇数

如果n为偶数

总结

前言

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

(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=1000000000
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];//这是用来存读入的数字的
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=1000000000
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;
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;
}


总结

这三种方法各有各的好处,但我们看看三种方法性能的对比(手打的我知道很丑..):

| 方法——当题状况————耗时——内存—代码长度 |

| 一

|

|———————————————————————|

| 二

|

|———————————————————————|

| 三

|

|———————————————————————|

所以这道题贪心是最好的!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息