题目描述
输入一个正整数 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
Leetcode链接:剑指offer面试题57 - II:和为s的连续正数序列
算法分析
- 本题使用滑动窗口法(双指针)解决,因为要找的是连续的正整数序列,所以可以用两个指针分别表示正整数序列的首尾(我用的是 i 和 j )。
- 具体实现:
当序列和 s u m < t a r g e t sum < target sum<target时,尾指针右移一位(很好理解,如果右移头指针的话序列和反而会变小)。
当序列和 s u m = t a r g e t sum = target sum=target时,通过一个内层循环将该序列加入res返回数组中。之后将头指针或者尾指针右移即可寻找下一个可能的序列和。
当序列和 s u m > t a r g e t sum > target sum>target时,将头指针右移一位使序列和减小。 - 注意,移动指针后更新序列和可以避免每次重复计算。
复杂度分析
问题规模为target的大小n
- 时间复杂度:O(
n
n
n),外层循环最多循环 target / 2 次,时间复杂度为O(
n
n
n)。
关于内层循环时间复杂度的计算:
由高斯求和公式:
( i + j ) ⋅ ( j − i + 1 ) ÷ 2 = t a r g e t , 令 t = j − i + 1 , ⟹ ( t + 2 ⋅ i − 1 ) ⋅ t = 2 ⋅ t a r g e t , 经 放 缩 ⟹ t 2 < 2 ⋅ t a r g e t ⟹ t < 2 ⋅ t a r g e t \begin{aligned} &(i + j) \cdot (j - i + 1) \div 2 = target, 令 t = j - i + 1, \\ \implies & (t + 2 \cdot i - 1) \cdot t = 2 \cdot target,经放缩 \\ \implies & t^2 < 2 \cdot target \\ \implies & t < \sqrt{2 \cdot target} \end{aligned} ⟹⟹⟹(i+j)⋅(j−i+1)÷2=target,令t=j−i+1,(t+2⋅i−1)⋅t=2⋅target,经放缩t2<2⋅targett<2⋅target
将和为target的正整数序列视为有效序列。此时求得的t为有效序列的长度。假设极限情况下,最长的有效序列的长度可以看作 2 ⋅ t a r g e t \sqrt{2 \cdot target} 2⋅target,且长度为2、3、···、 2 ⋅ t a r g e t \sqrt{2 \cdot target} 2⋅target的有效序列全部存在(由题易知确定长度的有效序列有且只有一个),可计算出内层循环里语句在程序运行期间的总执行次数为
t a r g e t + 2 ⋅ t a r g e t target + \sqrt{2 \cdot target} target+2⋅target
即O( n + n n + \sqrt{n} n+n),所以本题虽然用了双重循环,但是由于并不是每次外层循环都会执行内层循环,导致最终的时间复杂度仍然为O( n n n) - 空间复杂度:O( 1 1 1)
Golang代码如下
func findContinuousSequence(target int) (res [][]int) {
sum := 3
for i, j := 1, 2; i < j && j <= target / 2 + 1; {
if sum == target {
temp := make([]int, j-i+1)
for k := i; k <= j; k++ {
temp[k-i] = k
}
res = append(res, temp)
}
if sum >= target {
i++
sum -= i - 1
} else {
j++
sum += j
}
}
return
}