和为S的连续正数序列(数学技巧,事半功倍)

本文探讨如何快速找到所有和为S的连续正数序列,讲解双指针/滑动窗口法和纯数学技巧法。通过C++和Python代码实现,展示数学在算法中的应用,强调数学思维在解决问题上的重要性。
摘要由CSDN通过智能技术生成

  数学的基础在算法中的用处大概就是对数字的敏感性了,如果能找到数字的规律,在求解数字的时候往往就能根据规律快速求解。而普通的程序思维相比数学思维,一般就略微显得暴力。

题目描述

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

题目分析

  这个题目也算得上是给你一个数计算机能玩一天的题目,我们仍然从直观的思维开始一步步深入分析。
  要求这个序列,我们可以逐个数字去找从 i i i开始的序列,显然 i > 0 i>0 i>0,但是我们也没有必要所有序列都去找,毕竟以 i i i开始的序列,最多只有一个等于S,如果我们发现以 i i i开始,以 j j j结尾的序列和大于等于S了,显然后面就不用找了。此外,序列最少要包括两个数,显然如果 i ≥ S 2 i≥\frac{S}{2} i2S,那么两个数之和肯定大于S了,此时我们也不用找了。我们求和的时候每一次序列多一个数,所以求和的复杂度是 O ( 1 ) O(1) O(1),但是我们要找多少个序列,这个问题的复杂度就是多少,大致是 O ( S l g S ) O(SlgS) O(SlgS)的。

双指针/滑动窗口法

  我们要表示一个连续序列,我们只需要知道两个数的位置就够了,只需要知道从开始开始,到哪里结束。既然使用双指针的思想,那就可以参考和为S的两个数字,双指针解法。我们使用两个指针,一个记录序列的开始,一个记录序列的结束。
  如果两个指针记录的序列和小于S,则说明应该增大和,需要往序列里面添加数字,此时记录结尾的指针就需要后移,纳入更多的数字。如果序列和大于S,那么说明以序列开头的数字不可能存在和为S的序列了。此时需要减少数字,则开头指针后移。如果和等于S,则把这个序列记录,然后开头指针后移。这样的操作不会漏掉满足需求的值原理和引用的文章类似,有兴趣可以回去查看。
  我们来看一个例子,以S=9为例。初始化前指针pBeg=1,后指针pEnd=2。

pBegpEndsum 状态 S下一步操作
123小于9pEnd++
136小于9pEnd++
1410大于9pBeg++
249等于9(保存)pBeg++
347小于9pEnd++
3512大于9pEnd++
459等于9(保存)pBeg++,pBeg==5退出

  熟悉了这个过程,差不多可以写出代码了。

C++代码
class Solution {
public:
	vector<vector<int>>  FindContinuousSequence(int S) {
		vector<vector<int>> res;
		int pBeg = 1, pEnd = 2,sum=3,mid=S>>1;
		while (pBeg <= mid)
		{	
			if(sum>=S)
			{
				if (sum == S)
				{
					vector<int> tmp;
					for (int i = pBeg; i <= pEnd; i++)tmp.push_back(i);
					res.push_back(tmp);
				}
				sum -= pBeg;
				pBeg += 1;
			}
			else
			{
				pEnd += 1;
				sum += pEnd;
			}
		}
		return res;
	}
};
python代码
class Solution:
    def FindContinuousSequence(self, S):
        pBeg = 1
        pEnd = 2
        middle = (S + 1)>>1
        curSum = 3
        output = []
        while pBeg < middle:
            if curSum >= S:
                if curSum == S:
                    output.append(range(pBeg, pEnd + 1))
                curSum -= pBeg
                pBeg += 1
            else:
                pEnd += 1
                curSum += pEnd
        return output

  这里用的还是双指针或者叫滑动窗口的技巧求解,因为两个指针都是单向前进,没有回溯的过程,所以这个时间复杂度很好分析,就是 O ( S ) O(S) O(S)了,这个技巧还是蛮重要的,可以看到相比第一种分析复杂度已经进步了不少,因为这里已经利用了和的比较关系,省去了很多计算。

纯数学技巧法

  如果我们觉得上面的方法还是在找连续序列的和,这个方法就可以说是直接计算了。我们都知道如果让我们去求连续序列的和,我们肯定会知道一个公式,(首项+尾项)*项数/2。但是这里需要怎么用上这个公式呢。
  我们假设序列的首项是i,尾项是j。我们可以轻易求出 s u m = ( i + j ) ( j − i + 1 ) / 2 sum=(i+j)(j-i+1)/2 sum=(i+j)(ji+1)/2,我们把2移到等式左边可以得到 2 s u m = ( i + j ) ( j − i + 1 ) 2sum=(i+j)(j-i+1) 2sum=(i+j)(ji+1),显然我们有点二年级的数学水平就知道 ( i + j ) 和 ( j − i + 1 ) (i+j)和(j-i+1) (i+j)(ji+1)一个是奇数,一个是偶数。那现在就容易多了,我们直接求出sum的所有奇数因子,那么显然所有的奇数因子就构成了一组或两组解。可以通过质因数分解,把所有的质因数任意组合则得到所有的质数因子。
  为什么是一组或者两组,因为可能i解出来是个负数。我们不妨设这个奇数因子是odd,那么显然那个偶数因子就是 s u m ∗ 2 / o d d sum*2/odd sum2/odd,这个时候用两个数列两个方程:
{ o d d = i + j 2 s u m o d d = j − i + 1 a n d { o d d = i + j 2 s u m o d d = j − i + 1 \left \{ \begin{aligned} odd & = & i+j \\ \frac{2sum}{odd} & = & j-i+1 \\ \end{aligned} \qquad and \qquad \right. \left \{ \begin{aligned} odd & = & i+j \\ \frac{2sum}{odd} & = & j-i+1 \\ \end{aligned} \right. oddodd2sum==i+jji+1andoddodd2sum==i+jji+1
  通过这两个方程求解出 i i i j j j,然后舍去不符合条件的数值。如果这是一道选择题,到这里也就结束了。我下面给出一个参考代码。只知道在大数测试上,双指针根本无法等待出结果,这个方法很快就能算出来。因为需要质因数分解,难以评估复杂度,代码中需要保存下来的质数可以用于后续计算,这样计算的次数越多,省下来的时间就越多。

class Solution:
    prim = [2, 3, 5, 7, 11]
    def primeFactorization(self, tsum):
        for i in range(self.prim[-1]+1,int(tsum**0.5),2):
            flag=True
            for j in self.prim:
                if i%j==0:
                    flag=False
                    break
            if flag:
                self.prim.append(i)
        i=0
        fac=[]
        while i<len(self.prim):
            if tsum%self.prim[i]:
                i+=1
                continue
            fac.append(self.prim[i])
            tsum/=self.prim[i]
        return fac

    def FindContinuousSequence(self, tsum):
        fac=self.primeFactorization(tsum)
        odd=[i for i in fac if i%2]
        fact=set()
        res=[]
        mul=lambda x,y:x*y
        for i in range(len(odd)):
            l=permutations(odd,i+1)
            for j in l:
                a=reduce(mul,j)
                fact.add(a)
                fact.add((tsum<<1)//a)
        fact=sorted(list(fact))
        for i in fact:
            j=tsum*2//i
            if i<j+1:
                continue
            res.append(list(range((i-j+1)>>1,(i+j+1)>>1)))
        return res

  这种方法仅供参考,有利于思维方式的提高。总之一句话,数学能力在算法分析中会起到至关重要的作用,之中能力是需要慢慢累积出来的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值