1038 Recover the Smallest Number
大意:输入n个数字串, 输出这些串拼接成的最小的数字
32 321 3214 0229 87 能拼出的最小整数
229 321 3214 32 87
一开始的思路想的太简单,以为把数字串排个字典序即可。
试了一下就知道,按这个思路拼出来的是 229 32 321 3214 87, 显然不对。
然后我的思路就开始歪了。想着排序之后得到一个最小的估计值。再dfs搜索,遍历各种可能组合,用估计值去剪枝。写到一半发现, 判断两个串谁大谁小很简单,拼起来看看谁在前,谁在后会结果比较小即可。
例如 32 3213拼接 得到 323213 和 321232,明显第二个小,所以 3213在前会比较好。
问题就变成:为排序设计一个合适的比较函数即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
struct segment{
char s[17]={0}; //能容纳两个串,在 operator< 中只需复制2次串就行了。避免用临时数组复制4次字符串
char len=0;
bool operator<( segment& a )
{
strcpy( s+len, a.s);
strncpy( a.s+a.len, s, len); //按两种顺序拼接字符串
bool ret = strcmp( s, a.s )<0;
s[len]=a.s[a.len]=0; //恢复串结尾
return ret;
}
};
int main()
{
int n, digit=0;
scanf("%d", &n);
vector<segment> m(n);
segment tmp;
for( int i=0; i<n; i++ )
{
scanf("%s", tmp.s);
tmp.len = strlen( tmp.s );
digit += tmp.len;
m[i] = tmp ;
}
sort(m.begin(), m.end());
auto it = m.begin();
do{
sscanf( it->s, "%d", &n ); //跳过0的segment,单独输出第一个非0segment
++it;
}while( n==0 && it!=m.end()); //万一有全0的情况
printf("%d", n );
char *result = new char[digit+1]();
char *p=result;
for( ;it!=m.end(); ++it )
{
strcat( p, it->s ); //p指向目的串的末尾,减少strcat的遍历
p += it->len;
}
printf("%s", result);
}
//7 32 321 3214 0229 87 000 0000
这里之所以不用string是为了避免很多不必要的复制,以及精确控制串的占用的空间。
如果用string, 比较函数就会写成 a+b < b+a, 这里会构造两个临时string对象,复制ab 和 ba,随后再析构。
而我的比较函数之中,只需要一半的复制量。 并且不需要构造析构。
从结果来看,这份代码只需要 11ms 左右,内存700kb,
用了string的话,就要 50ms,900k。
使用c++ 中的对象,常会遇到很多临时对象的复制构造析构。
有些是可以避免的(通过定义良好的swap操作,移动复制,甚至特定专门的)但是这些会付出额外思考精力,牺牲可读性,通用性等等。
stl的string库针对通用目的设计的,确实很方便,但是也带来很多临时对象的复制构造问题,针对题目的特定情况,定制操作可以锻炼自己识别不必要复制的能力。