Smallest Difference(最小差值)三种角度求解
来源:互联网 发布:linux 文件引用计数 编辑:程序博客网 时间:2024/05/19 21:43
- 前言
- 题目
- 分析
- 一调用全排列函数
- 二手写全排列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,如图所示:
好了,接下来就有三种不同的方法:
一、调用全排列函数。
相信很多人第一眼看到这一题就想到了算法函数库(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;}
总结
这三种方法各有各的好处,但我们看看三种方法性能的对比(手打的我知道很丑..):
| 方法——当题状况————耗时——内存—代码长度 |
| 一 |
|———————————————————————|
| 二 |
|———————————————————————|
| 三 |
|———————————————————————|
所以这道题贪心是最好的!!!
- Smallest Difference(最小差值)三种角度求解
- [LintCode] 最小差 The Smallest Difference
- Smallest Difference
- Smallest Difference
- Smallest Difference
- Smallest Difference
- 387.The Smallest Difference-最小差(中等题)
- 387.The Smallest Difference-最小差(中等题)
- 最小差值
- POJ 2718 Smallest Difference
- Smallest Difference-模拟
- poj 2718 Smallest Difference
- poj2718 Smallest Difference
- Smallest Difference(贪心)
- POJ-2718-Smallest Difference
- poj 2718 Smallest Difference
- poj 2718 Smallest Difference
- POJ 2718-Smallest Difference
- webview运用和传值
- 多线程下载
- HDU 1257 最少拦截系统
- 数组中只出现一次的数字(数组)
- IjkMediaPlayer
- Smallest Difference(最小差值)三种角度求解
- Java基础总结第(3)天
- spring+mybatis的配置用法
- 第13周项目1-(1)验证折半查找算法
- 6. 二维数组和面向对象
- shell script中read的用法
- 设计模式系列-面向对象葵花宝典-面向对象实战
- Python微框架web.py初印象
- 论文笔记-Unconstrained Salient Object Detection via Proposal Subset Optimization