休了個不短不長的年假,題解系列繼續開工~
本專題旨在分享刷Leecode過程發現的一些思路有趣或者有價值的題目。
題目相關
- 原題地址: https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/]
題目描述:
輸入一個正整數 target ,輸出所有和為 target 的連續正整數序列(至少含有兩個數)。序列內的數字由小到大排列,不同序列按照首個數字從小到大排列。
- 示例 1:
輸入:target = 9
輸出:[[2,3,4],[4,5]] - 示例 2:
輸入:target = 15
輸出:[[1,2,3,4,5],[4,5,6],[7,8]]
限制:1 <= target <= 10^5
- 示例 1:
思路解析
暴力破解
題目的含義比較清晰,需要求出和為特定值的 連續正整數序列。
首先,這道題的很直接的也會想到一個 -- 暴力破解:
具體思路:
- 首先尋找是否存在滿足要求的,以1為開頭的序列,所以初始化一個序列為
[ 1 ]
依次往序列裡新增連續的正整數,讓序列變成
[1, 2, 3..i. target]
, 並且每次新增完一個整數時,對比當前序列所有整數之和sum,與目標值target的關係:- 如果
sum < target
,則繼續往序列裡新增下一個數; - 如果已經滿足
sum = target
,那麼儲存當前序列,並且說明以1開始的序列尋找可以停止了; - 如果直接到
sum > target
,那麼說明以1開始的序列尋找可以停止了(不存在以1為開頭並且滿足要求的序列);
- 如果
- 重複上述步驟,分別尋找以2,3, ... target開頭且滿足題意的序列;
上面這種思路的話 最壞的情況下,需要外層迴圈(外層迴圈也就是遍歷以1..n開頭的序列)n
次,內層迴圈n
次(內層迴圈就是尋找當前開頭固定時,不同結尾的序列),那麼總的複雜度就是O(n^2)
,由於 1 <= target <= 10^5
, 所以O(n^2)
明顯超時;
紅色區域表示序列,它的左邊界和右邊界有個特點,都只向右側移動,而整個遍歷的過程,其實就像是一個推拉窗的移動過程,這個演算法也就由此得名。
滑動視窗
從上述過程可以看到,暴力破解的問題在於時間複雜度太高,而之所以高,是因為在遍歷過程存在一些可以跳過的過程, 為了便於理解,我們帶入一個題設中的示例1,target = 9
的情況來進行演示。按照暴力破解思路:
- 首先序列為
[1]
, 序列之和sum = 1
, 1 < 9 繼續迴圈; - 序列為
[1, 2]
, 序列之和sum = 3
, 3 < 9 繼續迴圈; - 序列為
[1, 2,3]
, 序列之和sum = 6
, 6 < 9 繼續迴圈; - 序列為
[1, 2, 3 ,4]
, 序列之和sum = 12
, 12 > 9 ,那 Stop!;
到此說明不存在以1為開頭切滿足要求的序列,那麼按照前面的思路,接下來是要尋找以2開頭且滿足題意的序列,那麼現在問題來了:
我們真的有必要從[2]
開始嗎? 在找以1開頭的序列時,我們已經發現[1,2,3]
之和都小於target了,那序列[2,3]
之和肯定也小於9,那為什麼還要按部就班的,先走一次[2]
再到[2,3]
再到[2,3,4]
呢?
這,就是突破的關鍵!
所以我們發現,再找完以i開頭的序列之後,跳到尋找以i+1開頭的序列時,是可以跳過一些中間遍歷次數的,可以這麼做:
- 序列為
[1, 2, 3 ,4]
, 序列之和sum = 12, 12 < 9
,此時要停止尋找以1為開頭的序列,那麼我們直接去掉序列左邊的值,從[2,3,4]
開始尋找以2開頭的序列; - 按照規則,
[2, 3 ,4]
之和剛好為9
,此時儲存當前序列結果,並且停止尋找以2為開頭切滿足要求的序列,接下來準備尋找3開頭的序列,我們同樣去掉此時序列的最左邊值, 從[3, 4]
開始運算;
重複上述過程, 會發現,在遍歷過程中,我們的序列如下圖所示(懶得做動圖了,分開看更有利於理解):
紅色區域表示序列,它的左邊界
和右邊界
有個特點,都只向右側移動
,而整個遍歷的過程,其實就像是一個推拉窗
的移動過程,這個演算法也就由此得名。
當然,要使用上面的演算法,我們要回答一個問題:相較於暴力破解,滑動視窗確實減少了迴圈次數,但是滑動視窗能否找到所有的解呢?(也就是在上述的跳躍過程導致遺漏呢?)
這個是可以證明的,因為按照前文的遍歷思路:
- 尋找1開頭的序列時,只要序列之和小於
target
,則視窗右邊界一直往右擴充,直到找到[1,2,3]
時,此時序列值之和還是小於target
; 而到[1,2,3,4]
時,此時序列之和第一次大於target
,說明以1開頭的序列尋找結束; - 那麼此時以2開頭的序列
[2,3]
<[1,2,3]
<target
, 說明只需要從[2,3,4]開始尋找就可以了,(讀者朋友也可以拿示例2帶入試試看,加深理解)以此類推,說明滑動視窗的演算法是不會有遺漏的。
完整程式碼
到這裡只需要整理前面的思路,虛擬碼也就出來了:
- 初始化,設定序列視窗的左右邊界,分別為
1,2
,然後開始迴圈; - 迴圈,當序列內之和小於target時,右邊界右移;
- 迴圈過程如果發現序列值和等於target,則儲存當前序列,並且把左邊界右移;
- 迴圈過程如果發現序列值和大於target,則把左邊界右移;
- 當左邊界追上右邊界時,迴圈結束(可以思考下為什麼?)
那麼實際程式碼如下:
var findContinuousSequence = function(target) {
const res = [];
const sum = [0, 1];
let l = 1; // 左邊界
let r = 2; // 右邊界
let s = 3; // 當前序列之和sum
while(l < r){
if(s === target) {
// 滿足題意的序列新增到結果
res.push(getList(l, r));
}
if(s > target) {
s = s - l;
l++;
} else {
r++;
s += r;
}
}
return res;
};
function getList (l, r) {
const res = [];
for(let i = l; i<=r; i++) {
res.push(i)
}
return res;
}
那麼滑動視窗的內容就到此為止了~
此外...
在文章末尾順便打個小廣告,問下有沒有想來外企955的小夥伴:
- 面朝大海辦公,不打卡 不考勤 到點就下班,工作生活兩不誤;
- 假期超長(每年年假+帶薪病假+企業年假 = 20天起步!);
- 而且很多崗位可以長期遠端辦公! 不再困擾與一線高昂房價難落地的問題;
- 可內推,base杭州和廈門~