和为S的数字
题目描述
给定正整数n,求所有和为n的连续序列,如n = 100,则9,10,11,12,13,14,15,16是他的一个连续序列。(剑指Offer 57题)
解题思路
1.利用序列为连续的特性求解
由于这个序列是连续的那么设这个序列为(a1,a2,…an-1,an),其和Sn的值为k,那么当k < n
时为了增大k得到n,在序列中加入an+1,反之当 k > n
时取出当前序列中最小的元素a1,当n == k
时则为一个符合要求的解。为了寻找下一个解则继续在序列后加入下一个数。
由于和为n且序列为连续序列,当n为偶数时序列结尾肯定小于等于n/2
,因为(n / 2) + (n / 2) + 1 > n
,当n为奇数时(n / 2) * 2 + 1 == n
所以结尾肯定小于等于(n / 2) + 1
综上序列搜索的结尾为Math.ceil(n/2)
综上所述,我们可以从(1,2)序列开始往后搜索直到结尾数为Math.ceil(n/2)
代码如下
/**
*
* @param sum 序列和
* @return 所有符合要求的序列的集合
*/
public static List<List<Integer>> solve(int sum){
if(sum < 1)
throw new IllegalArgumentException("sum must greater than zero");
List<List<Integer>> result = new ArrayList<>();
if(sum < 3)//和小于3时不存在符合序列
return result;
//从1,2序列开始遍历所有可能
//end最大为sum一半的上取整,因为(sum / 2) + (sum / 2) == sum
//sum为偶数则(sum / 2) + (sum / 2) + 1 > sum
//sum为奇数时2 * (sum / 2 + 1) > sum
//now用来记录当前序列和防止每次重复计算
int beg = 1, end = 2, now = 3, half = (int) Math.ceil((double)sum/2);
while(end <= half && beg < end){
if(now == sum){
List<Integer> list = new ArrayList<>();
for(int i = beg;i <= end;i++)
list.add(i);
result.add(list);
/*
* 找到后末尾添加下一个元素继续搜索
* 设添加前首元素为a1,末尾为an-1,和为n
* 那么n + an - a1 > n,所以beg+=2
*/
end++;beg+=2;
now += end - (2 * beg -3);
}else if(now < sum){
//序列过小末尾增加下一个
end++;
now += end;//计算当前总和值
}
else{
//序列过大前面减少一个
now -= beg;
beg++;
}
}
return result;
}
2.利用数学公式求解
设这个序列为(a1,a2,…an-1,an),那么其和有公式如下