面試題:把負數移動到正數之前,不能改變正負數原先的次序


如:-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 提出的翻手例子:

0_1320473198JbmS

翻手代碼在時間和空間上都很高效,而且代碼非常的簡短,很難出錯。

於是方法是:將 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 走一遍就可以解決。同樣,具體舉例:

[……-+++--+-……]

  1. 剛開始掃描的時候如果一直是負數,那么不用作任何的動作
  2. 關鍵是遇上 [+++--] 的時候,需要作翻手處理,從而交換 [+++] 和 [--] 得到:[……---++++-……]
  3. 繼續掃描得到 [+],[……---++++-……],不用處理
  4. 繼續掃描得到 [-],[……---++++-……],需要對 [++++-] 作翻手處理,從而交換 [++++] 和 [-] 得到:[……----++++……](此步驟和步驟 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

http://daoluan.net


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM