如:-5,7,1,9,-12,15 變成 -5,-12,7,1,9,15。如何解?
題目要求:
空間復雜度O(1),時間復雜度O(N),排序穩定。
空間上只能利用循環變量,標記變量等;時間上可以說是過一遍數組就完事了。
分治
用分治可以解決問題:首先把規模為 N 的問題划分成兩個規模近似為 N/2 的子問題,兩個子問題得到解決后進行合並得到整個問題的答案。對於本篇的問題,主要考慮合並該怎么解決,也就是假設:
將數組 arr 分成 arr1 和 arr2。設 arr1 為 [----++++],arr2 為 [------+++],如何得到 arr 為 [----------+++++++]。
顯然,只要將 arr1 的所有正數和 arr2 的所有負數交換就好了,為了不打亂原有正數/負數的次序,而且不借用任何其他的空間,考慮用《編程珠璣》中提到的 Doug Mcllroy 提出的翻手例子:
翻手代碼在時間和空間上都很高效,而且代碼非常的簡短,很難出錯。
於是方法是:將 arr1 中的正數 reverse 一次,arr2 中的負數 reverse 一次,緊接着 arr1 中的正數和 arr2 中的 負數結合起來 reverse 一次,由此完成 arr1 和 arr2 的合並。子問題就解決了。
void merge(int *arr,int left,int middle,int right)
{
/*全負數*/ /*全正數*/
if(!(arr[middle]>0 && arr[middle+1]<0))
return;
// 找到 +++++ ----- 區域
int pos1 = left,pos2 = middle + 1;
for(int i=left; i<=middle; i++)
if(arr[i] > 0) {pos1 = i;break;}
for(i=middle+1; i<=right; i++)
if(arr[i] > 0) {pos2 = i-1;break;}
// 翻手定律,你懂得
reverse(arr+pos1,middle - pos1+1);
reverse(arr+middle+1,pos2 - middle);
reverse(arr+pos1,pos2 - pos1 + 1);
}
迭代
另外,通過翻手方法同樣可以迭代解決這個問題,也就是把 arr 走一遍就可以解決。同樣,具體舉例:
[……-+++--+-……]
- 剛開始掃描的時候如果一直是負數,那么不用作任何的動作
- 關鍵是遇上 [+++--] 的時候,需要作翻手處理,從而交換 [+++] 和 [--] 得到:[……---++++-……]
- 繼續掃描得到 [+],[……---++++-……],不用處理
- 繼續掃描得到 [-],[……---++++-……],需要對 [++++-] 作翻手處理,從而交換 [++++] 和 [-] 得到:[……----++++……](此步驟和步驟 2 情況相同)
void myfunc(int *arr,int left,int right)
{
if(right <= left)
return;
bool f = false; // flag = true 表示需要進行翻轉
int posbeg = -1, // 正數開始
posend, // 正數結束
negbeg, // 負數開始
negend, // 負數結束
negcount = 0; // 負數統計
for(int i = 0; i<=right; i++)
{
if(arr[i]>0)
{
if(posbeg < 0)
posend = posbeg = i;
posend = i;
for(int j=i+1; j<=right; j++)
if(arr[j]>0)posend++;
else break;
f = true;
i = posend + 1;
}
if(arr[i] < 0 && i <= right)
{
if(!f){negcount++;continue;}
negend = negbeg = i;
for(int j=i+1; j<=right; j++)
if(arr[j]<0)negend++,negcount++;
else break;
i = negend;
reverse(arr+posbeg,posend-posbeg+1);
reverse(arr+negbeg,negend-negbeg+1);
reverse(arr+posbeg,negend-posbeg+1);
f = false;print(arr,14);
posend = negend;
posbeg = negcount + 1;
}
}// while
}
算法復雜度
在空間上符合題目條件,但在時間上有待討論,時間上的消耗主要在 for 循環里面的 reverse 操作。假設 arr 數組當中有 m 個正數和 n-m 的負數,在最壞的情況下,復雜度是 O(m(n-m)),平攤給 for 循環的 n 次操作,那么結果是 O(m-m^2/n))。因此,此算法不穩定。
我們來看看根據 n 和 m 的不同模擬的函數圖像,將函數寫成 y = x-x^2/k:
由此,這種算法確實很不穩定。一開始以為平攤法可以解決復雜度分析問題,可以達到 O(N) 的效果,但無果而終。在 CSDN 上有這題的討論:騰訊面試題:把負數移動到正數之前,不能改變正負數原先的次序 和 微博的一個編程題 。如果你有什么好的想法,不忘給我和網友們分享一下 :),據說「之前百度就出過這道題,討論了1000樓都沒有一個正確答案」。
搗亂 2013-05-03


