劍指 Offer 57 - II. 和為s的連續正數序列
阿新 • • 發佈:2020-07-28
題目描述
我的題解
方法三雙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)]
- 當某序列含有奇數個(2k+1)元素時,Sequence : i, i+1, i+2 ... i+2k
-
因此列舉所有可能的序列長度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][]);
}