题目:在数组中,数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果。

分析:看到这个题目,很多人的第一反应是找到这个数组的最大值和最小值,然后觉得最大值减去最小值就是最终的结果。这种思路忽略了题目中很重要的一点:数对之差是一个数字减去它右边的数字。由于我们无法保证最大值一定位于数组的左边,因此这个思路不管用。

于是我们接下来可以想到让每一个数字逐个减去它右边的所有数字,并通过比较得到数对之差的最大值。由于每个数字需要和它后面的O(n)个数字作减法,因此总的时间复杂度是O(n2)。

解法一:分治法

通常蛮力法不会是最好的解法,我们想办法减少减法的次数。假设我们把数组分成两个子数组,我们其实没有必要拿左边的子数组中较小的数字去和右边的子数组中较大的数字作减法。我们可以想象,数对之差的最大值只有可能是下面三种情况之一:(1)被减数和减数都在第一个子数组中,即第一个子数组中的数对之差的最大值;(2)被减数和减数都在第二个子数组中,即第二个子数组中数对之差的最大值;(3)被减数在第一个子数组中,是第一个子数组的最大值。减数在第二个子数组中,是第二个子数组的最小值。这三个差值的最大者就是整个数组中数对之差的最大值。

在前面提到的三种情况中,得到第一个子数组的最大值和第二子数组的最小值不是一件难事,但如何得到两个子数组中的数对之差的最大值?这其实是原始问题的子问题,我们可以递归地解决。下面是这种思路的参考代码:

int MaxDiff_Solution1(int numbers[], unsigned length)
{if(numbers == NULL || length < 2)return 0;int max, min;return MaxDiffCore(numbers, numbers + length - 1, &max, &min);
}int MaxDiffCore(int* start, int* end, int* max, int* min)
{if(end == start){*max = *min = *start;return 0x80000000;}int* middle = start + (end - start) / 2;int maxLeft, minLeft;int leftDiff = MaxDiffCore(start, middle, &maxLeft, &minLeft);int maxRight, minRight;int rightDiff = MaxDiffCore(middle + 1, end, &maxRight, &minRight);int crossDiff = maxLeft - minRight;*max = (maxLeft > maxRight) ? maxLeft : maxRight;*min = (minLeft < minRight) ? minLeft : minRight;int maxDiff = (leftDiff > rightDiff) ? leftDiff : rightDiff;maxDiff = (maxDiff > crossDiff) ? maxDiff : crossDiff;return maxDiff;
}

在函数MaxDiffCore中,我们先得到第一个子数组中的最大的数对之差leftDiff,再得到第二个子数组中的最大数对之差rightDiff。接下来用第一个子数组的最大值减去第二个子数组的最小值得到crossDiff。这三者的最大值就是整个数组的最大数对之差。

解法二:转化成求解子数组的最大和问题

接下来再介绍一种比较巧妙的解法。如果输入一个长度为n的数组numbers,我们先构建一个长度为n-1的辅助数组diff,并且diff[i]等于numbers[i]-numbers[i+1](0<=i<n-1)。如果我们从数组diff中的第i个数字一直累加到第j个数字(j > i),也就是diff[i] + diff[i+1] + … + diff[j] = (numbers[i]-numbers[i+1]) + (numbers[i + 1]-numbers[i+2]) + ... + (numbers[j] – numbers[j + 1]) = numbers[i] – numbers[j + 1]。

分析到这里,我们发现原始数组中最大的数对之差(即numbers[i] – numbers[j + 1])其实是辅助数组diff中最大的连续子数组之和。我们在本系列的博客的第3篇《求子数组的最大和》中已经详细讨论过这个问题的解决方法。基于这个思路,我们可以写出如下代码:

int MaxDiff_Solution2(int numbers[], unsigned length)
{if(numbers == NULL || length < 2)return 0;int* diff = new int[length - 1];for(int i = 1; i < length; ++i)diff[i - 1] = numbers[i - 1] - numbers[i];int currentSum = 0;int greatestSum = 0x80000000;for(int i = 0; i < length - 1; ++i){if(currentSum <= 0)currentSum = diff[i];elsecurrentSum += diff[i];if(currentSum > greatestSum)greatestSum = currentSum;}delete[] diff;return greatestSum;
} 

解法三:动态规划法

既然我们可以把求最大的数对之差转换成求子数组的最大和,而子数组的最大和可以通过动态规划求解,那我们是不是可以通过动态规划直接求解呢?下面我们试着用动态规划法直接求数对之差的最大值。

我们定义diff[i]是以数组中第i个数字为减数的所有数对之差的最大值。也就是说对于任意h(h < i),diff[i]≥number[h]-number[i]。diff[i](0≤i<n)的最大值就是整个数组最大的数对之差。

假设我们已经求得了diff[i],我们该怎么求得diff[i+1]呢?对于diff[i],肯定存在一个h(h < i),满足number[h]减去number[i]之差是最大的,也就是number[h]应该是number[i]之前的所有数字的最大值。当我们求diff[i+1]的时候,我们需要找到第i+1个数字之前的最大值。第i+1个数字之前的最大值有两种可能:这个最大值可能是第i个数字之前的最大值,也有可能这个最大值就是第i个数字。第i+1个数字之前的最大值肯定是这两者的较大者。我们只要拿第i+1个数字之前的最大值减去number[i+1],就得到了diff[i+1]。

int MaxDiff_Solution3(int numbers[], unsigned length)
{if(numbers == NULL || length < 2)return 0;int max = numbers[0];int maxDiff =  max - numbers[1];for(int i = 2; i < length; ++i){if(numbers[i - 1] > max)max = numbers[i - 1];int currentDiff = max - numbers[i];if(currentDiff > maxDiff)maxDiff = currentDiff;}return maxDiff;
}

在上述代码中,max表示第i个数字之前的最大值,而currentDiff表示diff[i] (0≤i<n),diff[i]的最大值就是代码中maxDiff。

解法小结

上述三种代码,虽然思路各不相同,但时间复杂度都是O(n)(第一种解法的时间复杂度可以用递归公式表示为T(n)=2(n/2)+O(1),所以总体时间复杂度是O(n))。我们也可以注意到第一种方法是基于递归实现,而递归调用是有额外的时间、空间消耗的(比如在调用栈上分配空间保存参数、临时变量等)。第二种方法需要一个长度为n-1的辅助数组,因此其空间复杂度是O(n)。第三种方法则没有额外的时间、空间开销,并且它的代码是最简洁的,因此这是最值得推荐的一种解法。

博主何海涛对本博客文章享有著作权。网络转载请注明出处

http://zhedahht.blog.163.com/。整理出版物请和作者联系。对解题思路有任何建议,欢迎在评论中告知,或者加我微博 http://weibo.com/zhedahht或者 http://t.163.com/zhedahht与我讨论。谢谢。

程序员面试题精选100题(61)-数对之差的最大值[算法]相关推荐

  1. 程序员面试题精选100题(61)-数对之差的最大值

    题目:在数组中,数字减去它右边的数字得到一个数对之差.求所有数对之差的最大值.例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果. 分治策略: ...

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

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

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

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

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

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

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

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

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

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

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

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

  8. 程序员面试题精选100题(51)-顺时针打印矩阵

    // 程序员面试题精选100题(51)-顺时针打印矩阵.cpp : 定义控制台应用程序的入口点. //#include "stdafx.h" #include <iostre ...

  9. 程序员面试题精选100题:求从1到n的正数中1出现的次数

    // 程序员面试题精选100题(25):求从1到n的正数中1出现的次数 // 如 f(253) = (2!=0) * 100 + 2 * f(99) + (5!=0) * 10 + 5 * f(9) ...

最新文章

  1. 【Python】这款拓展让你的jupyter lab使用更高效
  2. Java学习笔记_字符串/静态static
  3. Android之运行PopupWindow提示Unable to add window -- token null is not valid; is your activity running?
  4. 高性能mysql_事务及4种隔离级别
  5. 大屏可视化分配率是什么意思_什么是分配率?
  6. 分段概率密度矩估计_考研数学:高数、线代、概率3科目知识框架梳理
  7. dataguru北京线下沙龙-第二部 《Oracle 索引优化思路--案例分享 -- 刘盛》
  8. [小技巧] ArrayList与LinkedList对比与常见方法
  9. Problem A: 删除区间内的元素(线性表)
  10. 数据驱动型企业如何炼成?只需五步!
  11. 13 个设计 REST API 的最佳实践
  12. OpenGL超级宝典(第7版)笔记22 原子计数器 清单5.31-5.34
  13. 魔百盒九联UNT402H,(芒果、南传、百视通)等通刷刷机固件
  14. python怎么测试opencv安装是否成功_测试opencv安装成功
  15. oss 视频 转码_oss视频转码----比阿里云文档更详细
  16. 站在巨人的肩膀上—英语
  17. CentOS 6.X yum源更新(阿里云默认最新更新为CentOS6.10)
  18. 【Python 实战基础】Pandas如何统筛选复制某个数据
  19. C++中的explicit
  20. 汽车零部件-线控底盘

热门文章

  1. 2017年含金量最高的机器学习技能或知识有哪些? 翻译 2017年10月20日 14:22:44 标签: 机器学习 / quora 7504 原文:As of 2017, what set of
  2. 十分钟学习自然语言处理概述
  3. 斯坦福CS231n 2017最新课程:李飞飞详解深度学习的框架实现与对比
  4. 深度学习(主要是CNN)用于图片的分类和检测总结
  5. 算法与数据结构(Java解八皇后问题)
  6. 高并发编程-线程通信_使用wait和notify进行线程间的通信2_多生产者多消费者导致程序假死原因分析
  7. C3P0-数据库连接池解读
  8. 60进制计算器在线_超长假期用华为MatePad Pro在线听课是怎样的体验?
  9. 学习笔记Hadoop(八)—— Hadoop集群的安装与部署(5)—— Hadoop配置参数介绍、Hadoop集群启动与监控
  10. java 事件分发线程_Java事件调度线程说明