829. Consecutive Numbers Sum

解法

解法一:判断每个长度是否合法

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+l1)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 1l2n+41 21
在这个范围内的l,只要a能算出来是正数就行了,因为算出来的a一定大于0
那么可以分类讨论:

  1. 如果l是奇数,明显需要n能整除l
  2. 如果l是偶数,那么l-1一定是奇数,由于 n l = a + l − 1 2 \frac{n}{l}=a+\frac{l-1}{2} ln=a+2l1,那么如果n能整除l,这事就一定不成立,等号左边不是整数;如果n不能整除l,但是2n能整除l,由于 2 n l = 2 a + ( l − 1 ) \frac{2n}{l}=2a+(l-1) l2n=2a+(l1) 2 n l \frac{2n}{l} l2n一定是奇数,否则n整除l就成立了,那么 2 n l − ( l − 1 ) \frac{2n}{l}-(l-1) l2n(l1)一定是偶数,可以找到对应的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+l1)
我们需要将2n拆成两个数的乘积,怎么拆呢?
首先注意到一点 l l l 2 a + l − 1 2a+l-1 2a+l1一定是一奇一偶的,那么我们可以把 2 n 2n 2n的偶数部分剥离出来,即令: 2 n = 2 α ⋅ m 2n=2^\alpha\cdot m 2n=2αm
其次还要注意第二点 2 a + l − 1 2a+l-1 2a+l1一定比 l l l大,甚至不可能相等

假如 m = x y m=xy m=xy,那么我们将可以得到两个可能性:

  1. 一个因子为 x x x,另一个为 y ⋅ 2 α y\cdot 2^\alpha y2α
  2. 一个因子为 y y y,另一个为 x ⋅ 2 α x\cdot 2^\alpha x2α

我们要想办法把这两种可能性分别赋给 l l l 2 a + l − 1 2a+l-1 2a+l1,由于在每种可能性里,两个因子的大小关系肯定是固定的,所以赋值方法只可能是把小的赋给 l l l,大的赋给 2 a + l − 1 2a+l-1 2a+l1

换句话说,一旦我们决定把 2 n 2n 2n分成 x x x y ⋅ 2 α y\cdot 2^\alpha y2α,那么 l l l是谁 2 a + l − 1 2a+l-1 2a+l1是谁就固定了。

那么由于 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个513,3,5,7
1个3,0个533,5,7
2个3,0个53,35,7
0个3,1个555,7
1个3,1个53,53,7
2个3,1个53,3,57

可以看出来,这基本实现了组合的分法,就是无序地分成两个整数乘积的分法,所以最后有序的分法就再翻倍就行了

那么如果最后一个数不是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

果然快很多呀……

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值