劍指Offer對答如流系列 - 和爲s的數字

面試題57:和爲s的數字

一、題目描述

問題(1)和爲s的兩個數字
輸入一個遞增排序的數組和一個數字s,在數組中查找兩個數,使得它們的和正好是s。如果有多對數字的和等於s,輸出任意一對即可。

問題(2)爲s的連續正數序列
輸入一個正數s,打印出所有和爲s的連續正數序列(至少含有兩個數)。例如輸入15,由於1+2+3+4+5=4+5+6=7+8=15,所以結果打印出3個連續序列1~5、4~6和7~8。

二、問題分析

問題(1)
考慮到數列遞增,我們設置兩個頭尾兩個索引i和j,

  1. 若ai + aj == sum,就是答案(相差越遠乘積越小)
  2. 若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的數已是不可能),j -= 1
  3. 若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 後面的數已是不可能),i += 1

由於是從兩邊往中間移動,所以不會有跳過的情況,時間複雜度爲O(n)。

問題(2)
順着問題(1)的思路來就是

  1. 當從i到j的序列的和小於sum時,增加j,使序列包含更多數字;(記得更新序列之和)
  2. 當從i到j的序列的和大於sum時,減少i,使序列去掉較小的數字;(記得更新序列之和)
  3. 當從i到j的序列的和等於sum時,此時得到一個滿足題目要求的序列,輸出,然後繼續將i增大,往後面找新的序列。

序列要求最少兩個數字,因此,當i到了sum/2時,就可以結束了。

不過,問題(2)也可以不借鑑問題(1)的思路,採用數學分析法

對於一個長度爲n的連續序列,如果它們的和等於s,有:

  1. 當n爲奇數時,s/n恰好是連續序列最中間的數字,即n滿足 (n&1)==1 && s%n==0
  2. 當n爲偶數時,s/n恰好是連續序列中間兩個數字的平均值,小數部分爲0.5,即n滿足 (s%n)*2==n (判斷條件中包含了n爲偶數的判斷)

得到滿足條件的n後,相當於得到了序列的中間數字s/n,所以可以得到第一個數字爲 (s / n) - (n - 1) / 2,結合長度n可以得到所有數字。

此外,要考慮在什麼範圍內找n: 我們知道n至少等於2,那至多等於多少?n最大時,序列從1開始,根據等差數列的求和公式根據等差數列的求和公式:S = (1 + n) * n / 2,可以得到n應該小於sqrt(2s),所以只需要從n=2到sqrt(2s)來判斷滿足條件的n,繼而輸出序列。

三、問題解答

問題(1)

    public ArrayList<Integer> FindNumbersWithSum(int [] array, int sum) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(array==null || array.length<=0) {
            return list;
        }
        int low=0;
        int high=array.length-1;
        while(low<high){
            if(array[low]+array[high]==sum){
                list.add(array[low]);
                list.add(array[high]);
                break;
            } else if(array[low]+array[high]<sum) {
                low++;
            } else {
                high--;
            }
        }
        return list;
    }

問題(2)
思路一:

	 public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer> > sequenceList = new ArrayList<ArrayList<Integer> >();
        if(sum<=0) {
            return sequenceList;
        }
        
        int small = 1;
        int big = 2;
        int curSum = small+big;
        while(small <= sum/2){
            if(curSum == sum){
                ArrayList<Integer> sequence = new ArrayList<>();
                for(int i=small;i<=big;i++) {
                    sequence.add(i);
                }
                sequenceList.add(sequence);
                curSum-=small;
                small++; //這兩行位置先後要注意
            }
            if(curSum < sum){
                big++;
                curSum+=big;
            }
            if(curSum > sum){
                curSum-=small;
                small++;
            }
        }
        return sequenceList;
    }

思路二:

   public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer> > sequenceList = new ArrayList<ArrayList<Integer> >();
        if(sum<=0) {
            return sequenceList;
        }
        
        for(int n=(int) Math.sqrt(2*sum); n>=2; n--){
            if(((n&1)==1 && sum%n==0) || ((n&1)==0 && (sum%n)*2==n)){
                ArrayList<Integer> sequence = new ArrayList<>();
                for (int j = 0, k = (sum / n) - (n - 1) / 2; j < n; j++, k++) {
                    sequence.add(k);
                }
                sequenceList.add(sequence);
            }
        }
        return sequenceList;
    }
發佈了194 篇原創文章 · 獲贊 3472 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章