如:-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