1. 程式人生 > 實用技巧 >劍指 Offer 57 - II. 和為s的連續正數序列

劍指 Offer 57 - II. 和為s的連續正數序列


本題 題目連結

題目描述


我的題解

方法三雙100%, 方法一 適合範圍廣

方法一:雙指標(也叫 滑動視窗)

思路分析

  • 用兩個指標i和表示當前列舉到的以i為起點,j為終點的區間,sum表示[i,j]的區間和:
    • 當sum < tiarget,j指標向前移動,擴大區間,增大區間和。即:j++,sum+=j ;
    • 當sum > target,i指標向前移動,收縮區間,減小區間和。即:sum-=i,i++;
    • 當sum == target,i指標向前移動2個單位,j向前移動一個單位。即sum = sum-i-(i+1),i-=2,j++,sum+=j;
      (當sum=target時,無論是 i 先向前移動,還是 j 先向前移動,另一個指標下一步都會向前一步,此時,一定有sum>target(剔除一個小的加進來一個大的數)。
      故而可以直接把 i++和 j++合成一步。又因sum>target,i 又會向前一步。故最終可以再把這兩步合成一步,直接令sum=target時,i 向前移動兩個單位而j向前移動1個單位)
  • (對於求區間和sum也可以用數學公式求啦:(連續序列和=(首項+末項)*項數/2))

程式碼如下

    public int[][] findContinuousSequence(int target) {
        ArrayList<int[]> resLists = new ArrayList<>();
        int sum = 1;
        for (int i = 1,j = 1; j <= (target >>1) + 1; ) {
            if (sum < target) {
                j++;
                sum += j;
            } else if (sum > target) {
                sum -= i;
                i++;
            } else {
                resLists.add(getArray(i,j));
                j++;
                sum = sum + j - i - (i + 1);
                i = i+2;
            }
        }
        return resLists.toArray(new int[0][]);
    }

    private int[] getArray(int i, int j) {
        if (i == j) return new int[0]; // 題目要求至少含2個數
        int[] arr = new int[(j - i + 1)];
        for (int k = 0; k <= j - i; k++) arr[k] = k + i;

        return arr;
    }

方法二:求根公式

(官方的題解,我就不寫了,直接截圖附上C++程式碼)

程式碼如下(C++)

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> vec;
        vector<int> res;
        int sum = 0, limit = (target - 1) / 2; // (target - 1) / 2 等效於 target / 2 下取整
        for (int x = 1; x <= limit; ++x) {
            long long delta = 1 - 4 * (x - 1ll * x * x - 2 * target);
            if (delta < 0) continue;
            int delta_sqrt = (int)sqrt(delta + 0.5);
            if (1ll * delta_sqrt * delta_sqrt == delta && (delta_sqrt - 1) % 2 == 0){
                int y = (-1 + delta_sqrt) / 2; // 另一個解(-1-delta_sqrt)/2必然小於0,不用考慮
                if (x < y) {
                    res.clear();
                    for (int i = x; i <= y; ++i) res.emplace_back(i);
                    vec.emplace_back(res);
                }
            }
        }
        return vec;
    }
};

方法三:等差數列特殊性質,奇偶討論,雙100

(參考了一位大佬的)

思路分析

  • 實際上某個序列可以按其包含奇數/偶數個元素來討論

    • 當某序列含有奇數個(2k+1)元素時,Sequence : i, i+1, i+2 ... i+2k
      其序列和為中間元素的2k+1倍,即:Sum(Sequence) = (2k+1) * (i+k)
    • 當某序列含有偶數個(2k)元素時, Sequence : i, i+1, i+2 ... i+2k-1
      其序列和為中間兩個元素的k倍,即:Sum(Sequence) = k * [(i+k-1) + (i+k)]
  • 因此列舉所有可能的序列長度len(從2開始,題目要求至少2個連續的數):

    • 奇數時直接判斷長度len是否整除target,整除則符合題意,找到序列
    • 偶數時判斷 2k*(mid1+mid2) = target,k為正整數,mid1和mid2為序列中間的兩個數,因連續,故和為大於1的奇數。
      成立,則找到序列
  • 由於列舉按照序列長度遞增順序,因此輸出時將結果逆序輸出

  • 對於列舉長度,len最大為:Math.sqrt(2+target)。理由如下:

程式碼如下


    public int[][] findContinuousSequence(int target) {
        ArrayList<int[]> resLists = new ArrayList<>();

        // 序列長度最大為 Math.sqrt(2+target);最小為2
        int len = 2;
        while (len * len < (target << 1)) {

            if (len % 2 == 1) { // 長度為奇數
                if (target % len == 0) { // 找到序列(中間數為 target/len)
                    int[] arr = new int[len];
                    for (int i = 0, val = target / len - len / 2; i < len; i++) // 存陣列
                        arr[i] = val++;
                    resLists.add(arr);
                }
            } else { // 長度為偶數
                int k = len / 2;
                // 符合式子: 2k*(mid1+mid2) = target,k為正整數,mid1和mid2為連續的兩個數,故和為大於1的奇數
                if (target % k == 0 && target / k % 2 == 1) { // 找到序列(中間兩個數的和為 target/k)
                    int[] arr = new int[len];
                    for (int i = 0, val = target / k / 2 - len / 2 + 1; i < len; i++)
                        arr[i] = val++;
                    resLists.add(arr);
                }
            }

            len++;
        }
        Collections.reverse(resLists);
        return resLists.toArray(new int[0][]);
    }