題目:輸入一個已經按升序排序過的數組和一個數字,在數組中查找兩個數,使得它們的和正好是輸入的那個數字。要求時間複雜度是O(n)。如果有多對數字的和等於輸入的數字,輸出任意一對即可。
例如輸入數組1、2、4、7、11、15和數字15。由於4+11=15,因此輸出4和11。
分析:如果我們不考慮時間複雜度,最簡單想法的莫過去先在數組中固定一個數字,再依次判斷數組中剩下的n-1個數字與它的和是不是等於輸入的數字。可惜這種思路需要的時間複雜度是O(n2)。
我們假設現在隨便在數組中找到兩個數。如果它們的和等於輸入的數字,那太 好了,我們找到了要找的兩個數字;如果小於輸入的數字呢?我們希望兩個數字的和再大一點。由於數組已經排好序了,我們是不是可以把較小的數字的往後面移動 一個數字?因爲排在後面的數字要大一些,那麼兩個數字的和也要大一些,就有可能等於輸入的數字了;同樣,當兩個數字的和大於輸入的數字的時候,我們把較大 的數字往前移動,因爲排在數組前面的數字要小一些,它們的和就有可能等於輸入的數字了。
我們把前面的思路整理一下:最初我們找到數組的第一個數字和最後一個數字。當兩個數字的和大於輸入的數字時,把較大的數字往前移動;當兩個數字的和小於數字時,把較小的數字往後移動;當相等時,打完收工。這樣掃描的順序是從數組的兩端向數組的中間掃描。
問題是這樣的思路是不是正確的呢?這需要嚴格的數學證明。感興趣的讀者可以自行證明一下。
參考代碼:
///////////////////////////////////////////////////////////////////////
// Find two numbers with a sum in a sorted array
// Output: ture is found such two numbers, otherwise false
///////////////////////////////////////////////////////////////////////
bool FindTwoNumbersWithSum
(
int data[], // a sorted array
unsigned int length, // the length of the sorted array
int sum, // the sum
int& num1, // the first number, output
int& num2 // the second number, output
)
{
// Find two numbers with a sum in a sorted array
// Output: ture is found such two numbers, otherwise false
///////////////////////////////////////////////////////////////////////
bool FindTwoNumbersWithSum
(
int data[], // a sorted array
unsigned int length, // the length of the sorted array
int sum, // the sum
int& num1, // the first number, output
int& num2 // the second number, output
)
{
bool found = false;
if(length < 1)
return found;
int ahead = length - 1;
int behind = 0;
while(ahead > behind)
{
long long curSum = data[ahead] + data[behind];
// if the sum of two numbers is equal to the input
// we have found them
if(curSum == sum)
{
num1 = data[behind];
num2 = data[ahead];
found = true;
break;
}
// if the sum of two numbers is greater than the input
// decrease the greater number
else if(curSum > sum)
ahead --;
// if the sum of two numbers is less than the input
// increase the less number
else
behind ++;
}
return found;
}
if(length < 1)
return found;
int ahead = length - 1;
int behind = 0;
while(ahead > behind)
{
long long curSum = data[ahead] + data[behind];
// if the sum of two numbers is equal to the input
// we have found them
if(curSum == sum)
{
num1 = data[behind];
num2 = data[ahead];
found = true;
break;
}
// if the sum of two numbers is greater than the input
// decrease the greater number
else if(curSum > sum)
ahead --;
// if the sum of two numbers is less than the input
// increase the less number
else
behind ++;
}
return found;
}
擴展:如果輸入的數組是沒有排序的,但知道里面數字的範圍,其他條件不變,如和在O(n)時間裏找到這兩個數字?
===============
擴展問題是不是先記數排序再用原來的方法?
如果是這樣的話,設數字範圍是d,則時間複雜度應該是O(max(d,n))
如果是這樣的話,設數字範圍是d,則時間複雜度應該是O(max(d,n))
是這個思路。如果記最小值爲min,最大值爲max。新建一個長度爲max-min+1的數組。初始化這個數組的每個元素爲0。掃描原數組每個元素k,在新數組中下標爲k-min的位置加1,這樣在O(n)的時間內把原數組轉換爲一個排好序的數組。接下來的做法一樣。
當然,時間複雜度標記爲O(max(d,n)) 更準確一些。謝謝指出。