【算法08】数对之差的最大值
题目:数组中,数字减去它右边(不要求相邻)的数字得到一个数对之差。求所有数对之差的最大值。例如:数组{2, 4, 1, 16, 7, 5, 11, 9},数对之差的最大值是11,是16-5的结果。
分析1:这道题我们同样可以用遍历的方法,固定一个数字,然后逐一减去它右边的数字,得到各个数对之差,然后比较选出最大值;对于数列中的每一个数字做如此操作,然后去所有最大值的最大值。然而和所有枚举算法的弊端一样,这样的算法效率比较低,所有的数对只差共有n*(n-1)/2,因而时间复杂度为O(n^2)。
分析2:联想到我们在“【算法05】左旋转字符串中”将一个大的字符分割成两个较小的字符串,然后逐一处理的分治思想。我们会想:我们能不能将这个数组分成两个部分呢?当然可以。我们可以将该数组分成两个子数组,所以该问题的解必然存在于以下三种情况中:
(1)被减数和减数都在第一个子数组,即第一个子数组数对之差的最大值;(2)被减数和减数都在第二子数组,即第二个子数组数对之差的最大值;(3)被减数在第一个子数组,减数在第二个子数组,此时容易知道被减速是第一个子数组中的最大值,减数是第二个子数组中的最小值。
因而我们的程序需要做的就是,记录第一个子数组的最大值,记录第二个子数组的最小值,而每一个子数组数对之差的最大值可以递归的方式解决,最小子问题,就是左右子数组都只有一个数字。基于这种思想,我们有如下的代码:
1 #include<iostream>
2 #include<string>
3 using namespace std;
4
5 int MaxDiffCore(int *start,int *end,int *max,int *min)
6 {
7 //最小子问题
8 if(start == end)
9 {
10 *max = *min = *start;
11 return 0;
12 }
13
14 int *middle = start + (end - start)/2;
15
16 //返回第一个子数组的最大值,最小值,最大数对之差
17 int maxLeft,minLeft;
18 int leftDiff = MaxDiffCore(start,middle,&maxLeft,&minLeft);
19
20 //返回第一个子数组的最大值,最小值,最大数对之差
21 int maxRight,minRight;
22 int rightDiff = MaxDiffCore(middle + 1,end,&maxRight,&minRight);
23
24 //计算被减数在第一子数组,减数在第二子数组的情况
25 int crossDiff = maxLeft - minRight;
26
27 *max = (maxLeft > maxRight) ? maxLeft:maxRight;
28 *min = (minLeft < minRight) ? minLeft:minRight;
29
30 int maxDiff = (leftDiff > rightDiff) ? leftDiff:rightDiff;
31 maxDiff = (maxDiff > crossDiff) ? maxDiff:crossDiff;
32 return maxDiff;
33 }
34
35
36 int MaxDiff(int array[],int arrayLength)
37 {
38 if(array == NULL || arrayLength < 2)
39 return -1;
40
41 int max,min;
42 return MaxDiffCore(array, array + arrayLength - 1, &max, &min);
43 }
44
45 int main()
46 {
47 cout<<"Enter the length of your array:"<<endl;
48 int n = 0;
49 cin>>n;
50
51 int *pArray = new int[n];
52 cout<<"Enter the elements in your array:"<<endl;
53 for(int i = 0;i < n;++i)
54 {
55 cin>>pArray[i];
56 }
57
58 int diff = MaxDiff(pArray,n);
59 cout<<"The maxDiff in your array is:"<<endl;
60 cout<<diff<<endl;
61
62 delete[] pArray;
63 return 0;
64 }
运行结果如下图所示:
分析3:一个有些技巧的算法,构造一个辅助数组diff[n-1],其中diff[i]=array[i]-array[i+1];这样diff[i]-diff[j]=(array[i]-array[i+1])+(array[i+1]-array[i+2])+......+(diff[j]-diff[j+1])=array[i]-array[j+1]。
这也就是说array[]的数对之差的最大值即是diff[]数组的最大子数组之和。而求一个数组的最大子数组之和,在【算法07】已经做过讨论,不再赘述,于是可以得到如下的代码:
1 #include<iostream>
2 #include<string>
3 using namespace std;
4
5 int MaxDiff(int array[],int arrayLength)
6 {
7 if(array == NULL || arrayLength < 2)
8 return 0;
9
10 //创建并初始化临时数组
11 int *diff = new int[arrayLength - 1];
12 for(int i = 0;i < arrayLength - 1;++i)
13 diff[i] = array[i] - array[i+1];
14
15 //求解临时数组的最大子数组之和
16 int currentSum = 0;
17 int largestSum = 0;
18 for(i = 0;i < arrayLength - 1;++i)
19 {
20 currentSum += diff[i];
21
22 if(currentSum < 0)
23 currentSum = 0;
24
25 if(currentSum > largestSum)
26 largestSum = currentSum;
27 }
28
29 if(largestSum == 0)
30 {
31 largestSum = diff[0];
32 for(int i = 0;i <arrayLength - 1;++i)
33 {
34 if(diff[i] > largestSum)
35 largestSum = diff[i];
36 }
37 }
38
39 delete[] diff;
40 return largestSum;
41 }
42
43 int main()
44 {
45 cout<<"Enter the length of your array:"<<endl;
46 int n = 0;
47 cin>>n;
48
49 int *pArray = new int[n];
50 cout<<"Enter the elements in your array:"<<endl;
51 for(int i = 0;i < n;++i)
52 {
53 cin>>pArray[i];
54 }
55
56 int diff = MaxDiff(pArray,n);
57 cout<<"The maxDiff in your array is:"<<endl;
58 cout<<diff<<endl;
59
60 delete[] pArray;
61 return 0;
62 }
运行结果如下:
分析4:我们还可以动态的考虑这个问题,我们可以假想这个数组是不断地往一个已知数组的前面加一个数字组成的,如果我们已经知道了已知数组的maxdiff,和已知数组的最小值,我们要怎么求解新数组的maxdiff呢,对了,我们只要用新加入的这个数组减去已知的最小值和maxdiff比较,取其较大者,同时和已知的最小值比较,取其较小者,更新最小值即可。最初最初的问题是什么?只有一个数字!最小值是它本身,maxdiff=0。根据这种思路我们可以写出如下的简洁代码:
1 #include<iostream>
2 #include<string>
3 using namespace std;
4
5 int MaxDiff(int array[],int arrayLength)
6 {
7 if(array == NULL || arrayLength < 2)
8 return 0;
9
10 int min = array[arrayLength - 1];
11 int maxdiff = 0;
12 for(int i = arrayLength - 2;i >= 0;--i)
13 {
14 maxdiff = (maxdiff > (array[i] - min)) ? maxdiff:(array[i] - min);
15 min = (min < array[i]) ? min:array[i];
16 }
17 return maxdiff;
18 }
19
20 int main()
21 {
22 cout<<"Enter the length of your array:"<<endl;
23 int n = 0;
24 cin>>n;
25
26 int *pArray = new int[n];
27 cout<<"Enter the elements in your array:"<<endl;
28 for(int i = 0;i < n;++i)
29 {
30 cin>>pArray[i];
31 }
32
33 int diff = MaxDiff(pArray,n);
34 cout<<"The maxDiff in your array is:"<<endl;
35 cout<<diff<<endl;
36
37 delete[] pArray;
38 return 0;
39 }
运行结果如下:
效率分析:分析2中的方法采用分治的思想,将分解问两个n/2的问题,因而其时间复杂度为O(n*logn),但是由于采用递归算法,递归调用在栈的参数保存,临时变量等。分析3中的方法用到长度为n-1的临时数组,空间复杂度为O(n),有上一篇的博文分析可知,时间复杂度也为O(n)。分析4中采用动态规划的方法时间复杂度为O(n),用min,maxdiff两个变量保存过程,因而空间复杂度为O(1)。是最为推荐的方法!
References:
【1】程序员面试题精选100题:http://zhedahht.blog.163.com/blog/static/2541117420116135376632/
【2】求递归算法的时间复杂度:http://www.cnblogs.com/wu8685/archive/2010/12/21/1912347.html
注:
1)本博客所有的代码环境编译均为win7+VC6。所有代码均经过博主上机调试。
2)博主python27对本博客文章享有版权,网络转载请注明出处http://www.cnblogs.com/python27/。对解题思路有任何建议,欢迎在评论中告知。