剑指offer刷题-和为S的连续正数序列

题目描述:

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

输出描述:

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

小白思路:

很简单,遍历小于sum的值,依次向后计算连续正数的和,当和小于sum时继续加,知道和>=sum,此时检查是否和等于sum以及是否为序列,若是,则添加到结果中。

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        
        ArrayList<ArrayList<Integer>> res= new ArrayList<ArrayList<Integer>>();
        for(int i = 1; i < sum ; i++)
        {
            int cur_sum = i;
            int cur_i = i;
            while(cur_sum < sum)
            {
                i += 1;
                cur_sum += i;
            }
            if(sum == cur_sum && i > cur_i)
            {
                ArrayList<Integer> nums = new ArrayList<Integer>();
                for(int j = cur_i; j<= i; j++)
                {
                    nums.add(j);
                }
                res.add(nums);
            }
        }
        return res;
    }
}

BUT:有错误,没搞懂是哪里错了,,后边争取补充
case通过率为50.00%
用例:
9
对应输出应该为:
[[2,3,4],[4,5]]
你的输出为:
[]

大牛思路:

一、双指针法
用两个指针small和big分别代表序列的最小值和最大值。令small从1开始,big从2开始。

当从small到big的序列的和小于s时,增加big,使序列包含更多数字;(记得更新序列之和)
  当从small到big的序列的和大于s时,增加small,使序列去掉较小的数字;(记得更新序列之和)
  当从small到big的序列的和等于s时,此时得到一个满足题目要求的序列,输出,然后继续将small增大,往后面找新的序列。
  序列最少两个数字,因此,当small到了s/2时,就可以结束判断了。

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer> >();
        if(sum <= 0)
            return res;
        int small = 1;
        int big = 2;
        int cur_sum = small + big;
        while(small <= sum/2)
        {
            if(cur_sum == sum)
            {
                ArrayList<Integer> nums = new ArrayList<Integer>();
                for(int i = small; i <= big; i++)
                {
                    nums.add(i);
                }
                res.add(nums);
                cur_sum -= small;
                small++;
            }
            else if (cur_sum > sum)
            {
                cur_sum -= small;
                small ++;
            }
            else
            {
                big ++;
                cur_sum += big;
            }
        }
        return res;
    }
} 

二、数学分析法

对于一个长度为n的连续序列,如果它们的和等于s,有:
  1)当n为奇数时,s/n恰好是连续序列最中间的数字,即n满足 (n&1)==1 && s%n == 0。表示n和1按位相与,如果相与结果为1,表示n对应的二进制位的最低位是1,即n是奇数
  2)当n为偶数时,s/n恰好是连续序列中间两个数字的平均值,小数部分为0.5,即n满足 (s%n)*2 == n (判断条件中包含了n为偶数的判断);不太懂表达式如何得到的。
  得到满足条件的n后,相当于得到了序列的中间数字s/n,所以可以得到第一个数字为 (s / n) - (n - 1) / 2,结合长度n可以得到所有数字。
  此外,在什么范围内找n呢?我们知道n至少等于2,那至多等于多少?n最大时,序列从1开始,根据等差数列的求和公式根据等差数列的求和公式:S = (1 + n) * n / 2,可以得到n应该小于sqrt(2s),所以只需要从**n=2到sqrt(2s)**来判断满足条件的n,继而输出序列。

public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) 
{    
	ArrayList<ArrayList<Integer> > sequenceList = new ArrayList<ArrayList<Integer> >();    
	if(sum<=0)        
		return sequenceList;         
	for(int n=(int) Math.sqrt(2*sum);n>=2;n--)
	{        
		if(((n&1)==1 && sum%n==0) || ((n&1)==0 && (sum%n)*2==n))
		{            
			ArrayList<Integer> sequence = new ArrayList<>();            
			for (int j = 0, k = (sum / n) - (n - 1) / 2; j < n; j++, k++) 
			{                
				sequence.add(k);            
			}            
			sequenceList.add(sequence);        
		}    
	}    
	return sequenceList;
}

分析:

1.还是利用两个指针,这个技巧要学会
  2.代码中求连续序列的和,并没有每次遍历计算,而是根据每次操作的情况而在之前的结果上进行加减,可以提高效率,值得学习
  3.指针从1,2开始,注意指针的初始设置。
  4.方法二中,当s/n的余数为0.5时,s%n的结果是n/2,而不是1,为啥子

PS:和为s的两个数字

题目描述:
  输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,输出任意一对即可。

小白思路一:
从头开始遍历数字,确定一个数字后,对后面的数字遍历,判断和是否为s,这种方法复杂度为O(n^2),效率太低。

大牛思路二:

我们考虑到,如果一个数字比较小,那么另一个数字一定比较大,同时数字为递增排列;所以,我们设置两个指针,一个指针small从第一个数字(最小)出发,另一个指针big从最后一个数字(最大)出发:
  当small加big的和小于s时,只需要将small指向后一个数字(更大),继续判断;
  当small加big的和大于s时,只需要将big指向前一个数字(更小),继续判断;
  当small加big的和等于s时,求解完成。
  由于是从两边往中间移动,所以不会有跳过的情况,时间复杂度为O(n)。

public class TwoNumbersWithSum {    
	public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {        
		ArrayList<Integer> list = new ArrayList<Integer>();        
		if(array==null || array.length<=0)            
			return list;        
		int low=0;        
		int high=array.length-1;        
		while(low<high)
		{            
			if(array[low]+array[high]==sum)
			{                
				list.add(array[low]);                
				list.add(array[high]);                
				break;            
			}
			else if(array[low]+array[high]<sum)                
				low++;            
			else                
				high--;       
		}        
		return list;    
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值