解法
解法一:判断每个长度是否合法
google了一下,大家都用的这种方法,假设某段连续数组起始为
a
a
a,长度为
l
l
l,那么想要加起来为n,则有
n
=
(
2
a
+
l
−
1
)
l
2
n=\frac{(2a+l-1)l}{2}
n=2(2a+l−1)l
首先我们可以判断出l的范围,由于a是正整数,所以能算出来:
1
≤
l
≤
⌊
2
n
+
1
4
−
1
2
⌋
1\le l\le\lfloor\sqrt{2n+\frac{1}{4}}-\frac{1}{2}\rfloor
1≤l≤⌊2n+41−21⌋
在这个范围内的l,只要a能算出来是正数就行了,因为算出来的a一定大于0
那么可以分类讨论:
- 如果l是奇数,明显需要n能整除l
- 如果l是偶数,那么l-1一定是奇数,由于 n l = a + l − 1 2 \frac{n}{l}=a+\frac{l-1}{2} ln=a+2l−1,那么如果n能整除l,这事就一定不成立,等号左边不是整数;如果n不能整除l,但是2n能整除l,由于 2 n l = 2 a + ( l − 1 ) \frac{2n}{l}=2a+(l-1) l2n=2a+(l−1), 2 n l \frac{2n}{l} l2n一定是奇数,否则n整除l就成立了,那么 2 n l − ( l − 1 ) \frac{2n}{l}-(l-1) l2n−(l−1)一定是偶数,可以找到对应的a
综上,代码如下:
class Solution(object):
def consecutiveNumbersSum(self, N):
"""
:type N: int
:rtype: int
"""
maxlen = int(math.sqrt(2*N+0.25)-0.5)
def check(l):
if l%2!=0:
return N%l==0
return N%l!=0 and 2*N%l==0
return sum(map(check, xrange(1,maxlen+1)))
解法二:进阶数学
这神仙解法想了好久才懂= =
步骤一:问题转化
还是基于等差数列求和公式:
2
n
=
l
(
2
a
+
l
−
1
)
2n=l(2a+l-1)
2n=l(2a+l−1)
我们需要将2n拆成两个数的乘积,怎么拆呢?
首先注意到一点,
l
l
l和
2
a
+
l
−
1
2a+l-1
2a+l−1一定是一奇一偶的,那么我们可以把
2
n
2n
2n的偶数部分剥离出来,即令:
2
n
=
2
α
⋅
m
2n=2^\alpha\cdot m
2n=2α⋅m
其次还要注意第二点,
2
a
+
l
−
1
2a+l-1
2a+l−1一定比
l
l
l大,甚至不可能相等
假如 m = x y m=xy m=xy,那么我们将可以得到两个可能性:
- 一个因子为 x x x,另一个为 y ⋅ 2 α y\cdot 2^\alpha y⋅2α
- 一个因子为 y y y,另一个为 x ⋅ 2 α x\cdot 2^\alpha x⋅2α
我们要想办法把这两种可能性分别赋给 l l l和 2 a + l − 1 2a+l-1 2a+l−1,由于在每种可能性里,两个因子的大小关系肯定是固定的,所以赋值方法只可能是把小的赋给 l l l,大的赋给 2 a + l − 1 2a+l-1 2a+l−1。
换句话说,一旦我们决定把 2 n 2n 2n分成 x x x和 y ⋅ 2 α y\cdot 2^\alpha y⋅2α,那么 l l l是谁 2 a + l − 1 2a+l-1 2a+l−1是谁就固定了。
那么由于 m = x y m=xy m=xy和 m = y x m=yx m=yx是一样的分法,上述的两个可能性我们可以简单地归类为把偶数因子往后面那个因子上乘。
所以最后,问题转化为:将m拆分成两个整数的乘积(有序),有多少种拆分方式
步骤二:求因子分解数目
官方解法三
这个算法也很神奇:
ans = 1
d = 3 # 由于是给奇数拆分,肯定从因子3开始
while d * d <= N:
e = 0
while N % d == 0:
N /= d
e += 1
# e统计了N有多少个因子d
ans *= e + 1 # 有e+1种使用因子d的方式,即取0,1,...,e个因子d
d += 2
# 到这里剩下的N一定是个大于d的质数
# N一定大于d,如果它是小于等于d的奇数,在前面统计小于等于d的因子的时候就会被除掉了,所以有:d<N<d*d
# N一定是质数(或1),因为假设N是合数,那么它一定是由至少2个大于d的因子组成的,那不可能会出现d*d>N
if N > 1: ans *= 2
我们不是要把N分解成两个整数的乘积嘛,那么ans
其实代表着前一个数的可能性。
我们以315为例,它的因子为:{3:2,5:1,7:1}
通过代码里的注释的分析我们知道,在最后一句前,7是一定不会被前一个整数取的。
取法 | 前一个数 | 后一个数 |
---|---|---|
0个3,0个5 | 1 | 3,3,5,7 |
1个3,0个5 | 3 | 3,5,7 |
2个3,0个5 | 3,3 | 5,7 |
0个3,1个5 | 5 | 5,7 |
1个3,1个5 | 3,5 | 3,7 |
2个3,1个5 | 3,3,5 | 7 |
可以看出来,这基本实现了组合的分法,就是无序地分成两个整数乘积的分法,所以最后有序的分法就再翻倍就行了
那么如果最后一个数不是7而是1呢?那么不难看出它直接实现了有序的分解,不需要再乘以2了。(当然在这个例子里如果直接用45不会令最后N为1,假如是9、27等等数就有可能了)
最后整个问题的代码为:
class Solution(object):
def consecutiveNumbersSum(self, N):
"""
:type N: int
:rtype: int
"""
while N&1==0:
N = N>>1
ans = 1
d =3
while d*d<=N:
e = 0
while N%d==0:
e += 1
N = N/d
ans *= e+1
d += 2
if N>1: ans*=2
return ans
果然快很多呀……