一個整數可以由其他若干個連續整數的和表示(java)

題目的具體描述:
一個整數N,他可能等於比它小的若干個整數(大於2)相加。如果存在這樣的連續整數,將他們輸出,如果沒有則不輸出。
例: 整數18,18=5+6+7=3+4+5+6。所以輸出[5 6 7],[3 4 5 6]。

這是一個公司給出的筆試編程題,當時我沒做出來,當時一直在想總結規律,寫出效率很高的代碼,結果最後連一個可行方案都寫出來。值得反思,當時時間有限,應該先寫一個可行方案,在去考慮優化的,當時沒有編譯器進行調試,也不好測試優化的結果。

後面花了兩個小時,終於把優化後的算法寫出來了。沒有網上查資料,完全自己思考,因爲題目本身並不難。

public class ArithmeticSquence {

    public static void main(String args[]){
        long startTime = System.currentTimeMillis();//獲取當前時間
        arithmetic_squence(100000000);
        long endTime = System.currentTimeMillis();//獲取當前時間
        System.out.println("程序運行時間:"+(endTime-startTime)+"ms");
    }

    public static void arithmetic_squence(int n){

        int comma=0;//用來作爲記錄有多少組連續整數,也用來在輸出連續整數時添加逗號的標記
        for(int i=2;i<=n/2+1;i++){//i表示該整數由i個連續整數相加

            int value=n/i-i/2;//當該整數由i個連續整數相加時,這些連續整數的最小值的表達式(不是特別精確)

            if (value<0)//當計算到value<0時,說明此時已經到邊緣了,不能再有其他組合了
                break;
            else if(value<1)//0<=value<1時,這時候剛好到邊緣,是最後有可能的組合
                value=1;


            for(int j=value;j<=n/i+1;j++){//在可能的範圍內循環判斷可能出現的組合
                int temp=(j+j+i-1)*i/2;//等差數列,首相加末項乘以項數除以2
                if(temp>n)//如果大於0,那就說明這裏的組合不可能滿足要求了
                    break;
                else if(temp==n){//成功找到連續整數

                    //註釋部分是爲了在控制檯輸出連續整數
//                  if(comma!=0)
//                      System.out.print(",");
//                  
//                  System.out.print("[");
//                  
//                  for(int k=0;k<i;k++){
//                      if(k!=0)
//                          System.out.print(" ");
//                      System.out.print(j+k);
//                  }
//                  System.out.print("]");

                    comma++;//記錄有多少組連續整數組合
                    break;
                }

            }   
        }
        System.out.println(comma);
    }
}

對於整個算法的嚴整型,我只驗算了前20個數字,我也不會去證明,如果有bug,請指出。

說說整個思路

1、連續整數,就可以理解爲公差爲1的等差數列。然後用求和公式去驗證。

2、對於整數N,假如它等於兩個連續整數相加,那麼這兩個整數,是不是就是N/2附近的兩個數,這個好理解把。那麼如果等於3個連續整數相加,那麼是不是就是N/3附近的三個數。如果它等於N/2(向下取整,java裏默認是這樣的)個整數相加時,那麼N/(N/2)=2,也就是2附近的數,那2附近的數其實就是1和3,也就是說,N=N/2個整數相加已經是一種極限的了(我自己理解的,至於有沒有特例,不敢肯定)。
所以第一個for循環裏i的判斷條件就可以求得,再加1是因爲java裏對int型整除是默認向下取整的,並不是四捨五入,爲保證不漏掉極限的那種情況,就加一個1。

3、第二個for循環,舉個例子理解。對於整數N=18,假設由兩個連續整數相加,那麼就是18/2=9,9周圍有兩種組合,8+9=17<18,9+10=19>18。所以18不能由兩個連續整數相加,假設由3個整數相加,18/3=6,那麼取6附近的數有4,5,6,7,8。4+5+6=15,5+6+7=18,6+7+8=21。其實這裏你就可以看到,當一個整數N等於其他n個連續整數相加時,它一定是由N/n附近的整數組成,而且應該一定包含N/n(取整)這個整數(沒有證明過,所以不敢說肯定),並且至少包含這個整數左邊或者右邊的一位數。所以int value=n/i-i/2,value就是這樣得來的(這裏更縮小爲i/2,覺得有問題可以改成value=n/i-i+1,並且注意這裏的n不等於括號外面那個n,而是等於N),並且value取的是可能範圍裏最小的那個數,然後從最小值一直往上加n個數,看看是否會等於N,如果某個時候相加已經大於N了,那麼說明不存在n個連續整數相加等於N。

4、if (value<0) break;代碼裏有這麼兩行,這兩行提高了整個代碼很多的效率。可以去掉這裏,僅保留if(value<1) value=1試試,效率完全不在一個等級。因爲此時假設整數N等於i個連續整數相加,而這時候可能範圍的最小值已經小於0< N/i<1了,此時還是可以滿足包含N/i這個整數並且至少包含它左邊或者右邊的一個整數(此時只能選右邊)。那麼如果N等於i+1個連續整數相加時,value的值只會更小,而且i+1>3,其實這時候必須包含N/i左邊以及右邊的兩個整數,那麼顯然不可能滿足了(這裏0不能算)。

主要的優化思路就在上面了。再說明一次,上面算法只經過實踐證明,沒有嚴格的理論證明,不敢保證一定沒有bug。

對於整數100000000(一億),有8個連續整數組合相加等於它,只需要5ms(不輸出到控制檯,和機器有關,我電腦配置很一般,約3500元的組裝機)。首先,數字越大,需要循環的次數也就越多,一億已經夠大了,5ms這個效率,應該可以接受了吧。

~~~~~~~~~~~~~~~~~~~~~~~
一下內容是網上查資料學習到的:
對於int value=n/i-i/2。
假設一個整數可以由連續整數相加求得。那麼有N=(value+value+i-1)*i/2(value代表連續整數中最小的那個),那麼value=N/i-(i-1)/2,這個value的公式會更嚴謹,所以上面也改成這個更好。

改進後代碼

public class test2 {

    public static void main(String args[]){
        long startTime = System.currentTimeMillis();//獲取當前時間
        arithmetic_squence(20);
        long endTime = System.currentTimeMillis();//獲取當前時間
        System.out.println("程序運行時間:"+(endTime-startTime)+"ms");
    }

    public static void arithmetic_squence(int n){

        int comma=0;//用來作爲記錄有多少組連續整數,也用來在輸出連續整數時添加逗號的標記
        for(int i=2;i<=n/2+1;i++){//i表示該整數由i個連續整數相加

            int value=n/i-(i-1)/2;//當該整數由i個連續整數相加時,這些連續整數的最小值的表達式

            if(value<1)//最小取1
                break;

                int temp=(value+value+i-1)*i/2;//等差數列,首相加末項乘以項數除以2

                if(temp==n){//成功找到連續整數

                    //註釋部分是爲了在控制檯輸出連續整數
//                  if(comma!=0)
//                      System.out.print(",");
//                  
//                  System.out.print("[");
//                  
//                  for(int k=0;k<i;k++){
//                      if(k!=0)
//                          System.out.print(" ");
//                      System.out.print(value+k);
//                  }
//                  System.out.print("]");

                    comma++;//記錄有多少組連續整數組
            }   
        }
        System.out.println(comma);
    }

}

果然,算法優化無止盡。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章