題目:
陣列中某數字減去其右邊的某數字得到乙個數對之差,求所有數對之差的最大值。
例如:陣列
中,數對之差的最大值是
11(16 - 5)
分析:
看到這個題目,很多人的第一反應是找到這個陣列的最大值和最小值,然後覺得最大值減去最小值就是最終的結果。
但由於我們無法保證最大值一定位於陣列的左邊,因此這個思路不管用。
讓每乙個數字逐個減去它右邊的所有數字,並通過比較得到數對之差的最大值,
總的時間複雜度是
o(n2)。
解法1:分治法(遞迴實現)
通常蠻力法不會是最好的解法,我們想辦法減少減法的次數。假設我們把陣列分成兩個子陣列,我們其實沒有必要拿左邊的子陣列中較大的數字去和右邊的子陣列中較小的數字作減法,因為數對之差的最大值只有可能是下面三種情況之一 (
1)被減數和減數都在第乙個子陣列中,即第乙個子陣列中的數對之差的最大值;(2
)被減數和減數都在第二個子陣列中,即第二個子陣列中數對之差的最大值;(3
)被減數在第乙個子陣列中,是第乙個子陣列的最大值;減數在第二個子陣列中,是第二個子陣列的最小值。 (
1)、(
2)、(
3)中,
這三個差值的最大者就是整個陣列中數對之差的最大值。
在前面提到的三種情況中,得到第乙個子陣列的最大值和第二子陣列的最小值不是一件難事,但如何得到兩個子陣列中的數對之差的最大值?這其實是原始問題的子問題,我們可以遞迴地解決,參考**:
// 解法1: 分治法(遞迴實現)
int maxdiff_find(int *start, int *end, int *max, int *min)
int *mid = start + (end - start)/2;
int maxleft, minleft;
int leftdiff = maxdiff_find(start, mid, &maxleft, &minleft); // 左子陣列
int maxright, minright;
int rightdiff = maxdiff_find(mid+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;
}int maxdiff(int array, unsigned int len)
int max, min;
int maxdiff_num = maxdiff_find(array, array+len-1, &max, &min);
printf("maxdiff_num: %d\n\n", maxdiff_num);
}
解法2:轉化法(求子陣列的最大和問題)
較為深入分析此問題,發現可以轉化成求子陣列最大和問題。
1、有一陣列array[n],陣列長度為n
2、構建乙個長度為n-1的輔助陣列array2[n-1],且array2[i] = array[i] - array[i+1]; (0<=i
3、如果累加輔助陣列array2從i到j(j>i),即array[i] + array2[i+1] + ... + array2[j],
有(array[i] - array[i+1]) + (array[i+1] -array[i+2]) + ... + (array[j] - array[j+1])
= array[i] - array[j+1]
分析至此,發現陣列中最大的數對之差(即
array[i] - array[j+1]
)其實是輔助陣列
array2
中最大的連續子陣列之和。如何求連續子陣列最大之和,見前一篇部落格陣列中最大和的子陣列,在此直接給出參考**:
// 解法2: 轉化求解子陣列的最大和問題
int maxdiff(int array, unsigned int len)
int *array2 = new int[len - 1]; // 輔助陣列
int i = 0;
for(i=0; imaxsum)
} if(array2)
printf("maxsum: %d\n", maxsum);
}
解法3:動態規劃法
解法2,既然可以把求最大的數對差轉換成求子陣列的最大和,而子陣列的最大和可以通過動態規劃求解,那是不是可以通過動態規劃直接求解呢?下面我們試著用動態規劃法直接求數對之差的最大值。
1、定義maxdiff[1]
是以陣列中第
i個數字為減數的所有數對之差的最大值,也就是說對於任意j(
j < i
),maxdiff[1]
≥array[j]-array[i]
。以此類推,
maxdiff[i]
(0≤i)的最大值就是整個陣列最大的數對之差。
2、現在我們假設已經求得了
maxdiff[i]
,我們該怎麼求得
maxdiff[i+1]
呢?對於
maxdiff[i]
,肯定存在乙個j(
j < i
),滿足
array[j]
減去array[i]
之差是最大的,也就是
array[j]
應該是array[i]
之前的所有數字的最大值。當我們求
maxdiff[i+1]
的時候,我們需要找到第
i+1個數字之前的最大值。
第i+1
個數字之前的最大值有兩種可能:
這個最大值可能是第
i個數字之前的最大值,也有可能這個最大值就是第
i個數字。
第i+1
個數字之前的最大值肯定是這兩者的較大者,
我們只要拿第
i+1個數字之前的最大值減去
array[i+1]
,就得到了
maxdiff[i+1]
。參考**:
// 解法3: 動態規劃法
void maxdiff(int array, unsigned int len)
int max = array[0];
int maxdiff = max - array[1]; // 初始化數對差(array[0]-array[1])
int i = 0;
for(i=2; imax)
int curdiff = max - array[i]; // 始終用最大值 - 右側元素值
if(curdiff > maxdiff)
} printf("maxnum: %d\n", maxdiff);
}
測試結果:
解法小結:
上述三種解法,雖然思路各不相同,但時間複雜度都是o(n)
第一種方法是基於遞迴實現,而遞迴呼叫是有額外的時間、空間消耗的(比如在呼叫棧上分配空間儲存引數、臨時變數等)。
第二種方法需要乙個長度為n-1的輔助陣列,因此其空間複雜度是o(n)。
第三種方法則沒有額外的時間、空間開銷,並且它的**是最簡潔的,因此這是最值得推薦的一種解法。
原始碼
陣列中數對差最大
題目 陣列中某數字減去其右邊的某數字得到乙個數對之差,求所有數對之差的最大值。例如 陣列中,數對之差的最大值是11 16 5 分析 看到這個題目,很多人的第一反應是找到這個陣列的最大值和最小值,然後覺得最大值減去最小值就是最終的結果。但由於我們無法保證最大值一定位於陣列的左邊,因此這個思路不管用。讓...
陣列中數對差最大
題目 陣列中某數字減去其右邊的某數字得到乙個數對之差,求所有數對之差的最大值。例如 陣列中,數對之差的最大值是 11 16 5 分析 看到這個題目,很多人的第一反應是找到這個陣列的最大值和最小值,然後覺得最大值減去最小值就是最終的結果。但由於我們無法保證最大值一定位於陣列的左邊,因此這個思路不管用。...
2021 02 28陣列中數對差最大
題目 陣列中某數字減去其右邊的某數字得到乙個數對之差,求所有數對之差的最大值。例如 陣列中,數對之差的最大值是11 16 5 分析 看到這個題目,很多人的第一反應是找到這個陣列的最大值和最小值,然後覺得最大值減去最小值就是最終的結果。但由於我們無法保證最大值一定位於陣列的左邊,因此這個思路不管用。讓...