[软件工程师面试题精选100题]61.数对之差的最大值
题目
在数组中,数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果。
思路一
看到这个题目,很多人的第一反应是找到这个数组的最大值和最小值,然后觉得最大值减去最小值就是最终的结果。这种思路忽略了题目中很重要的一点:数对之差是一个数字减去它右边的数字。由于我们无法保证最大值一定位于数组的左边,因此这个思路不管用。
于是我们接下来可以想到让每一个数字逐个减去它右边的所有数字,并通过比较得到数对之差的最大值。由于每个数字需要和它后面的O(n)个数字作减法,因此总的时间复杂度是O(n^2)。
思路二
利用动态规划实现:
设dp[i]为第i个元素(包括第i个元素)之前元素的最大值。
动态规划方程为:
dp[i] = max(dp[i-1],num[i])
时间复杂度:O(n)
空间复杂度:O(n)
代码二
/*-------------------------------------
* 日期:2015-03-25
* 作者:SJF0115
* 题目: 61.数对之差的最大值
* 来源:程序员面试题精选100题
* 博客:
------------------------------------*/
#include <iostream>
#include <vector>
using namespace std;
int MaxDiff(int num[],int n){
if(n <= 1){
return 0;
}//if
vector<int> dp(n,0);
// 计算i之前元素的最大值(包括第i个元素)
dp[0] = num[0];
for(int i = 1;i < n;++i){
dp[i] = max(dp[i-1],num[i]);
}//for
// 计算最大的数对之差
int Max = num[0] - num[1];
for(int i = 2;i < n;++i){
Max = max(Max,dp[i-1] - num[i]);
}//for
return Max;
}
int main(){
int num[] = {2,4,1,16,7,5,11,9};
cout<<MaxDiff(num,8)<<endl;
return 0;
}
思路三
对上面的思路进行优化一下。
我们不单独开辟一个数组来存储当前元素之前的最大元素值,而是利用当前元素的前一个位置来记录当前元素之前的最大值。
时间复杂度:O(n)
空间复杂度:O(1)
代码三
/*-------------------------------------
* 日期:2015-03-25
* 作者:SJF0115
* 题目: 61.数对之差的最大值
* 来源:程序员面试题精选100题
* 博客:
------------------------------------*/
#include <iostream>
#include <vector>
using namespace std;
int MaxDiff(int num[],int n){
if(n <= 1){
return 0;
}//if
// 计算最大的数对之差
int Max = num[0] - num[1];
num[1] = max(num[0],num[1]);
for(int i = 2;i < n;++i){
Max = max(Max,num[i-1] - num[i]);
num[i] = max(num[i-1],num[i]);
}//for
return Max;
}
int main(){
int num[] = {2,4,1,16,7,5,21,9};
//int num[] = {1,2,3,4,5,6,7,8};
cout<<MaxDiff(num,8)<<endl;
return 0;
}
思路四 分治法
通常蛮力法不会是最好的解法,我们想办法减少减法的次数。假设我们把数组分成两个子数组,我们其实没有必要拿左边的子数组中较小的数字去和右边的子数组中较大的数字作减法。我们可以想象,数对之差的最大值只有可能是下面三种情况之一:(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。这三者的最大值就是整个数组的最大数对之差。