剑指offer——和为S的连续正数数列

今天在牛客网上刷剑指offer,遇到一个跟上篇博客有相似之处的算法题。先附上上一篇博客的链接。

剑指offer——输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? 

方法一:

咋一看好像是有点类似,共同点:递增正数列、和一定。(emmmmm.... 那有怎样呢?)

此时你可能会问:“之前的那道题不是才两个数吗? 这道题你怎么知道有几个数”,的确,这道题是正数序列,但是这题的正数序列有一个明显特征:连续。连续的正数,也是公差为1的等差数列呗,这样我们就可以将连续数列的和转换为两个数的和,S=(首项+尾项)*项数/2=(low+high)*(high-low+1)/2;这样我们就将多个数的和,转换为只跟首尾两个数有关了,那我们怎么确定首尾呢?我们之前的夹逼法,在判断两个数的和与S之间的大小关系的时候,若大于S,high--,r若小于S,low++;那这道题是否也采用夹逼的方法进行解决呢?很明显不可以,为什么呢?因为当low+high>S时,让low+high更接近S的方法,只可能是high--;但是这道题,当low+high>S时,正数数列的和更接近S的方法有两个:high--或low++; 在与S比较的时候,不能确定是high--还是low++。所以夹逼方法在这是不可行的。

此时我们应该转换一下思路,在两个数相加的的时候,我们是通过high--或low++的方式,不管是high--还是low++,都是在最小化的逼近S。那么,我们在正数序列中元素的个数不确定的情况下,如何让每一次与S判断之和的操作更加逼近S呢?

只能从最小的正数序列开始,也就是{1,2},即low=1,high=2;判断a=(low+high)*(high-low+1)/2是否与S相等,若a<S,则为了变化最小,且要接近S,只能high++;若a>S,只能low++;

代码如下:

	/**
	 * @param sum
	 * @return 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
	 */
	private static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
		ArrayList<ArrayList<Integer>>  list =  new ArrayList<ArrayList<Integer>>();
		int low = 1,high =2 ;  //从最小的正整数序列开始
		int  res = 0;
		while (low < high) {  
			res = (low+high)*(high-low+1)/2;    //根据数列求和公式,求以low、high为首尾项数列的和
			if (res == sum) {
				ArrayList<Integer> arrayList =  new ArrayList<Integer>();
				for (int i = low; i <= high; i++) {
					arrayList.add(i);
				}
				list.add(arrayList);
				high++;    //为了输出所有的结果,在这做low++或high++操作,使得首尾指针发生改变,求出其他满足条件的情况。
			}else if (res < sum) {
				high++;
			}else
			{
				low++;
			}
		}
		return list;	
	}

无论是求两个和为S的两个数,还是求和为S的正数序列,共同的思路都是转换为首尾指针的变化,通过首尾指针的变化来使和缓慢接近S,直到相等。

方法二

第二种方法是根据本题的特征加上数学的方法得到的。观察可以很容易得到,当正数序列的个数为奇数或偶数时,都会有其特征。当序列个数为奇数时,很明显18,19,20,21,22序列中,中位数与平均数相等,也就是说当序列个数为奇数时,只需要知道序列的个数就可以得到完整序列。当序列为偶数时,中间两个数相加的和乘以项数就等于S;

根据以上,方法也可以得到正确的解答,代码如下:

private static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
		ArrayList<ArrayList<Integer>>  list =  new ArrayList<ArrayList<Integer>>();
		for (int i = sum / 2 + 1; i > 1; i--) {
			//i表示正数序列的个数
			if (i % 2 == 0) { 		 //序列元素个数为偶数个时
				int mid= sum / i;	//[9, 10, 11, 12, 13, 14, 15, 16] ,mid就相当于 12 
				int temp = i / 2;	// 项数为4	
				//中间两项相加*项数,若不等于sum,则个数有误,直接continue.
				if (mid < temp || (mid+mid+1)*temp != sum) {
					continue;
				}
				ArrayList<Integer> arrayList = new ArrayList<Integer>();
				//j的初始值,也就是首项不能小于0,在上面已经作校验
				for (int j = mid - temp + 1; j <= mid+temp; j++) {
					arrayList.add(j);
				}
				list.add(arrayList);
			}else
			{
				if (sum % i !=0) {   //当个数为奇数,平均值不为整数,说明个数有误,直接continue
					continue;
				}
				else
				{
					int mid = sum / i;
					int temp = i / 2;
					if (mid <= temp) {
						continue;
					}
					ArrayList<Integer> arrayList = new ArrayList<Integer>();
					for (int j = mid - temp; j<= mid+temp; j++) {
						if (j==0) {
							System.out.println(mid+","+temp);
						}
						arrayList.add(j);
					}
					list.add(arrayList);
				}
			}
		}
		
		return list;	
	}

可能大部分人第一想到的就是方法二吧,纯数学思想,也很简单,但是要考虑到边界条件,不然会有bug。

上述如果存在有问题的地方请多指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值