目录
1. 问题描述
给定一个正整数 n
,返回 连续正整数满足所有数字之和为 n
的组数 。
示例 1:
输入: n = 5 输出: 2 解释: 5 = 2 + 3,共有两组连续整数([5],[2,3])求和后为 5。
示例 2:
输入: n = 9 输出: 3 解释: 9 = 4 + 5 = 2 + 3 + 4
示例 3:
输入: n = 15 输出: 4 解释: 15 = 8 + 7 = 4 + 5 + 6 = 1 + 2 + 3 + 4 + 5
提示:
1 <= n <= 10^9
2. 思路与算法1
首先进行必要的数学分析。
以下进行分类讨论。考虑n是否能表达为k个连续整数之和。
【case1:考虑k为奇数】
显然必须满足条件n能够被k整除,记。这个不是充要条件,还需要满足。这是因为如果n表达为k个连续整数之和且k为奇数,则m为这连续k个整数的中间数,所以m的左边至少必须要还有个正整数。
【case2:考虑k为偶数】
如果k个整数之和为n,记前半部分的和为x1,后半部分的和为x2,必然有,即:
进一步x1必须能够表达为k/2个连续整数之和。这个成为原问题的一个较小规模的问题,因此可以考虑以动态规划方法(递归+Memoization)来实现。
在最外层对k进行扫描即可,但是扫描的上限需要注意。概算如下:考虑k为奇数,则中间那个数为m=n/k,这k个连续整数应为{ },由此可得:
。
class Solution:
def consecutiveNumbersSum(self, n: int) -> int:
memo = dict()
def dp(m:int ,k:int) -> bool:
#print('dp: ',m,k)
if k==1:
return True
if (m,k) in memo:
return memo[(m,k)]
if k%2 == 1:
# k为奇数
memo[(m,k)] = m%k==0 and ((m//k) > ((k-1)//2))
return memo[(m,k)]
else:
# k为偶数
tmp = m-(k//2)**2
if tmp % 2 == 1:
memo[(m,k)] = False
return False
else:
memo[(m,k)] = dp( tmp // 2, k//2 )
return memo[(m,k)]
cnt = 0
for k in range(1,int(sqrt(2*n))+1):
cnt += dp(n,k)
#print(memo)
return cnt
执行用时:672 ms, 在所有 Python3 提交中击败了5.39%的用户
内存消耗:167.6 MB, 在所有 Python3 提交中击败了5.39%的用户
3. 思路与算法2
看了看官解,关于k为偶数的分析还能更进一步,如下所示。
令x为该k个连续整数中的最小正整数,则有,所以必然有k不能整除n但是能整除2n。也就是说“k不能整除n但是能整除2n”是“n可以表达为k个连续整数之和”的必要条件。
接下来证明“k不能整除n但是能整除2n”其实是“n可以表达为k个连续整数之和”的充分条件。
由于“k不能整除n但是能整除2n”,所以2n/k一定是奇数(否则就与n不能被k整除矛盾了),令,其中m是整数,则有。此时,
由于k是偶数,因此x也肯定是整数,因此,n可以表达为从x开始的k个连续整数之和。
class Solution:
def consecutiveNumbersSum(self, n: int) -> int:
cnt = 0
for k in range(1,int(sqrt(2*n))+1):
if k%2 == 1:
# k为奇数
#cnt += n%k==0 and ((n//k) > ((k-1)//2))
cnt += n%k==0 # 后面那个约束条件不需要,因为上面扫描的上界决定了它一定会满足
else:
cnt += (n % k) != 0 and (2*n % k ) == 0
return cnt
执行用时:116 ms, 在所有 Python3 提交中击败了53.92%的用户
内存消耗:15.1 MB, 在所有 Python3 提交中击败了6.37%的用户
回到总目录:Leetcode每日一题总目录(动态更新。。。)