题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。例如输入数组{32,  321},则输出这两个能排成的最小数字32132。请给出解决问题的算法,并证明该算法。

分析:这是09年6月份百度新鲜出炉的一道面试题,从这道题我们可以看出百度对应聘者在算法方面有很高的要求。

这道题其实是希望我们能找到一个排序规则,根据这个规则排出来的数组能排成一个最小的数字。要确定排序规则,就得比较两个数字,也就是给出两个数字m和n,我们需要确定一个规则m和n哪个更大,而不是仅仅只是比较这两个数字的数值哪个更大。

根据题目的要求,两个数字m和n排成的数字mn和nm,如果mn<nm,那么我们应该输出mn,也就是m应该排在n的前面,也就是m小于n;反之,如果nm<mn,n小于m。如果mn==mn,m等于n。

接下来我们考虑怎么去拼接数字,即给出数字m和n,怎么得到数字mn和nm并比较它们的大小。直接用数值去计算不难办到,但需要考虑到的一个潜在问题是m和n都在int能表达的范围内,但把它们拼起来的数字mn和nm就不一定能用int表示了。所以我们需要解决大数问题。一个非常直观的方法就是把数字转换成字符串。

另外,由于把数字m和n拼接起来得到的mn和nm,它们所含有的数字的个数肯定是相同的。因此比较它们的大小只需要按照字符串大小的比较规则就可以了。

基于这个思路,我们可以写出下面的代码:

// Maxinum int number has 10 digits in decimal system
const int g_MaxNumberLength = 10;// String buffers to combine two numbers
char* g_StrCombine1 = new char[g_MaxNumberLength * 2 + 1];
char* g_StrCombine2 = new char[g_MaxNumberLength * 2 + 1];// Given an array, print  the minimum number
// by combining all numbers in the array
void PrintMinNumber(int* numbers, int length)
{if(numbers == NULL || length <= 0)return;// Convert all numbers as stringschar** strNumbers = (char**)(new int[length]);for(int i = 0; i < length; ++i){strNumbers[i] = new char[g_MaxNumberLength + 1];sprintf(strNumbers[i], "%d", numbers[i]);}// Sort all strings according to algorithm in function compareqsort(strNumbers, length, sizeof(char*), compare);for(int i = 0; i < length; ++i)printf("%s", strNumbers[i]);printf("\n");for(int i = 0; i < length; ++i)delete[] strNumbers[i];delete[] strNumbers;
}// Compare two numbers in strNumber1 and strNumber2
// if [strNumber1][strNumber2] > [strNumber2][strNumber1],
// return value > 0
// if [strNumber1][strNumber2] = [strNumber2][strNumber1],
// return value = 0
// if [strNumber1][strNumber2] < [strNumber2][strNumber1],
// return value < 0
int compare(const void* strNumber1, const void* strNumber2)
{// [strNumber1][strNumber2]strcpy(g_StrCombine1, *(const char**)strNumber1);strcat(g_StrCombine1, *(const char**)strNumber2);// [strNumber2][strNumber1]strcpy(g_StrCombine2, *(const char**)strNumber2);strcat(g_StrCombine2, *(const char**)strNumber1);return strcmp(g_StrCombine1, g_StrCombine2);
}

上述代码中,我们在函数compare中定义比较规则,并根据该规则用库函数qsort排序。最后把排好序的数组输出,就得到了根据数组排成的最小的数字。

找到一个算法解决这个问题,不是一件容易的事情。但更困难的是我们需要证明这个算法是正确的。接下来我们来试着证明。

首先我们需要证明之前定义的比较两个数字大小的规则是有效的。一个有效的比较需要三个条件:1.自反性,即a等于a;2.对称性,即如果a大于b,则b小于a;3.传递性,即如果a小于b,b小于c,则a小于c。现在分别予以证明。

1.

自反性。显然有 aa=aa,所以 a=a。

2.       对称性。如果a小于b,则ab<ba,所以ba>ab。因此b大于a。

3.       传递性。如果a小于b,则ab<ba。当a和b用十进制表示的时候分别为l位和m位时,ab=a×10m+b,ba=b×10l+a。所以a×10m+b<b×10l+a。于是有a×10m-a< b×10l –b,即a(10m -1)<b(10l -1)。所以a/(10l -1)<b/(10m -1)。

如果b小于c,则bc<cb。当c表示成十进制时为m位。和前面证明过程一样,可以得到b/(10m -1)<c/(10n -1)。

所以a/(10l -1)< c/(10n -1)。于是a(10n -1)<c(10l -1),所以a×10n +c<c×10l +a,即ac<ca。

所以a小于c。

在证明了我们排序规则的有效性之后,我们接着证明算法的正确性。我们用反证法来证明。

我们把n个数按照前面的排序规则排好顺序之后,表示为A1A2A3…An。我们假设这样排出来的两个数并不是最小的。即至少存在两个x和y(0<x<y<n),交换第x个数和地y个数后,A1A2…Ay…Ax…An<A1A2…Ax…Ay…An

由于A1A2…Ax…Ay…An是按照前面的规则排好的序列,所以有Ax<Ax+1<Ax+2<…<Ay-2<Ay-1<Ay

由于Ay-1小于Ay,所以Ay-1Ay<AyAy-1。我们在序列A1A2…Ax…Ay-1Ay…An交换Ay-1和Ay,有A1A2…Ax…Ay-1Ay…An<A1A2…Ax…AyAy-1…An(这个实际上也需要证明。感兴趣的读者可以自己试着证明)。我们就这样一直把Ay和前面的数字交换,直到和Ax交换为止。于是就有A1A2…Ax…Ay-1Ay…An<A1A2…Ax…AyAy-1…An< A1A2…Ax…AyAy-2Ay-1…An<…< A1A2…AyAx…Ay-2Ay-1…An

同理由于Ax小于Ax+1,所以AxAx+1<Ax+1Ax。我们在序列A1A2…AyAxAx+1…Ay-2Ay-1…An仅仅只交换Ax和Ax+1,有A1A2…AyAxAx+1…Ay-2Ay-1…An<A1A2…AyAx+1Ax…Ay-2Ay-1…An。我们接下来一直拿Ax和它后面的数字交换,直到和Ay-1交换为止。于是就有A1A2…AyAxAx+1…Ay-2Ay-1…An<A1A2…AyAx+1Ax…Ay-2Ay-1…An<…< A1A2…AyAx+1Ax+2…Ay-2Ay-1Ax…An

所以A1A2…Ax…Ay…An< A1A2…Ay…Ax…An。这和我们的假设的A1A2…Ay…Ax…An <A1A2…Ax…Ay…An相矛盾。

所以假设不成立,我们的算法是正确的。

本文已经收录到《剑指Offer——名企面试官精讲典型编程题》一书中,有改动,书中的分析讲解更加详细。欢迎关注。

本题已被九度Online Judge系统收录,欢迎读者移步到http://ac.jobdu.com/hhtproblems.php在线测试自己的代码。

博主何海涛对本博客文章享有版权。网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。

程序员面试题精选100题(41)-把数组排成最小的数[算法]相关推荐

  1. 程序员面试题精选100题(42)-旋转数组的最小元素[算法]

    题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个排好序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的 ...

  2. 程序员面试题精选100题(03)-子数组的最大和[算法]

    题目:输入一个整形数组,数组里有正数也有负数.数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和.求所有子数组的和的最大值.要求时间复杂度为O(n). 例如输入的数组为1, -2, 3, ...

  3. 程序员面试题精选100题:41-50解题报告

    程序员面试题精选100题(41)-把数组排成最小的数[算法]   题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个.例如输入数组{32,  321},则输出这两个能 ...

  4. 程序员面试题精选100题(17)-把字符串转换成整数[算法]

    题目:输入一个表示整数的字符串,把该字符串转换成整数并输出.例如输入字符串"345",则输出整数345. 分析:这道题尽管不是很难,学过C/C++语言一般都能实现基本功能,但不同程 ...

  5. 程序员面试题精选100题(16)-O(logn)求Fibonacci数列[算法]

    题目:定义Fibonacci数列如下: /  0                      n=0 f(n)=      1                      n=1         \  f ...

  6. 程序员面试题精选100题(26)-和为n连续正数序列[算法]

    题目:输入一个正数n,输出所有和为n连续正数序列. 例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5.4-6和7-8. 分析:这是网易的一道面试题. 这道题和 ...

  7. 程序员面试题精选100题(29)-调整数组顺序使奇数位于偶数前面[算法]

    题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分.要求时间复杂度为O(n). 分析:如果不考虑时间复杂度,最简单的思路应该是从头扫描这个数组,每 ...

  8. 程序员面试题精选100题

    程序员面试题精选100题(01)-把二元查找树转变成排序的双向链表 题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表.要求不能创建任何新的结点,只调整指针的指向. 比如将二元查找树   ...

  9. [程序员面试题精选100题]13.第一个只出现一次的字符

    [题目] 在一个字符串中找到第一个只出现一次的字符.如输入abaccdeff,则输出b. [分析] [代码] /********************************* * 日期:2013- ...

最新文章

  1. android中menu菜单扩增_【已解决】Android添加Menu菜单
  2. 【c语言】扶老奶奶过街
  3. Adaboost(自适应提升树)算法原理
  4. poj 1995 Raising Modulo Numbers 二分快速幂
  5. 产品经理专业知识50篇(十二)-抖音训练营项目实战(知识付费MVP产品模型)
  6. 线程池是如何重复利用空闲的线程来执行任务的?
  7. Vue全局API总结
  8. hdoj 1114 Piggy-Bank(完全背包+dp)
  9. 2.8 循环语句介绍
  10. 抛砖引玉——Stagefright漏洞初探
  11. html设计的概念,HTML标签, 元素与列表,前端UI设计-2019.08.30
  12. Mac软件下载站推荐,让你告别软件荒!
  13. 全屏滚动,微场景,H5全屏切换滚动页面制作的方法介绍
  14. MAX31865模块PT100两线制测温
  15. oracle ora-3136,ORA-3136 错误解决 .
  16. C语言 switch多分支语句实现根据等级划分分数段
  17. 浅谈 Fhq-Treap
  18. PYTHON从娃娃学起教程 教案 第一课计算机简史
  19. ns2 + GT-ITM
  20. android闹钟哪个好用,谁才是轻便实用好闹钟 Android闹钟类应用横评

热门文章

  1. 百度语音识别技术负责人李先刚:如何利用Deep CNN大幅提升识别准确率?
  2. 后年将有60亿部手机!
  3. 计算机二级考试常用代码,二级计算机VB考试常用代码(看完必过).doc
  4. SpringBoot - 子模块下spring-boot-configuration-processor不生效问题
  5. RocketMQ-初体验RocketMQ(11)-过滤消息_自定义Java类筛选消息
  6. 第八届java c组,2015年Java方向C组第八题
  7. pdb连接数_Oracle 19c 之多租户 PDB 连接与访问(三)
  8. Compliments 计算机术语,2016年9月统考计算机复习题.doc
  9. localdate获取几个月前_关于近期使用java8中LocalDateTime的总结
  10. php 光速,PHP的光速教程