运用模逆运算(同余方程)来解决Matlab课上的一道思考题

一道Matlab编程题 & 暴力解法

Matlab课上老师出了这样一道题:
一个篮子有K个鸡蛋:
2个2个拿剩1个;
3个3个全部拿完;
4个4个拿剩1;
5个5个拿剩4个;
6个6个拿剩3个;
7个7个拿全部拿完;
8个8个拿剩1个;
9个9个拿全部拿完;
求篮子里鸡蛋的个数K

虽然这是一道matlab拿来玩的题目,可是我觉得完全可以拿来做笔试题或者面试题。仔细想还是有点考算法能力的。


这道题直观地想是非常简单的,简单一想就可以发现鸡蛋个数一定是7,9,3的最小公倍数63的N倍,然后我们就可以把63的倍数枚举出来,然后分别与除以2~9的除数,判断余数是否是对应值就可以了。当然你也可以用点观察法,当然你可以通过一点简单的观察来得到结果的规律来减少枚举次数。
 
matlab的算法代码如下:
resultNum = [];
    resultIndex = 0;
    i = 1;
    while 63*i < 1000000000
        while 1
            i = i + 2;
            if max(abs(rem(63*i, inputDivisors) - modResult)) == 0
                break
            end 
        end
        resultNum(resultIndex + 1) = 63*i;
        resultIndex = resultIndex +1;
    end
    disp(resultIndex)
    resultNum( resultIndex)

 

如果把这道题目一般化,比如有N个鸡蛋,i次i次拿,拿M次,然后求鸡蛋个数在N以内的取值。那枚举肯定是最慢的方法了,算法复杂度一眼看出来就是O(MN) 。显然,当M和N都很大时,这个时间复杂度是很大的,我们要想个办法把它简化。
 
 
从另一个角度看问题

我们先来看下枚举本身为什么会慢的问题,我们可以看到,枚举每一次都要尝试和M个除数进行相除,然后得到余数,除了本身求余这种对CPU的ALU指令非常不友好导致速度有瓶颈以外,还有最致命的就是枚举要通过不断地 “试验” 来得到判断的目的,当然这个过程可以裁枝(就像上面所说的观察法),但是这个问题的裁枝也只是减少了复杂度MN前面的常数系数。
 
既然裁枝的方法是制约着整个整个算法的瓶颈,那么我们要想能不能不裁枝,而是通过直接用一个一定成立的方程(也就是算子)来推出所有的结果呢?
 
我们可以联想到算勾股定理(找出A^2 + B^2 == C^2)所有成立的公式的时候,这是某些Java书里面最喜欢考的弱鸡算法题了,第一眼反应的时候肯定是用个三重循环来枚举(复杂度O(N^3)),仔细想一下,可以用Hash表裁枝(复杂度O(N^2)),但是我们如果使用的是完全平方公式4M^2N^2 == (M^2 + N^2)^2 - (M^2 - N^2)^2来思考这道题,我们可以发现我们只用枚举O(sqrt(M^2 + N^2)*sqrt(M^2 - N^2))次(也就是O(N))就可以找到所有的结果了,因为我们已经构建了一个一定成立的算子来填充结果了,而不需要再用枚举去匹配。

同样的,我们这道算法题也可以用同样的思路,我们观察一下可以得到,如果一个式子要满足结果对M个除数的余数都是成立的,那么结果必然是某些余数为0的除数的倍数。而根据余数定理可知,一个式子的对某个数的余式一定是一个多项式,所以我们立马就可以得到我们的结果一定是满足某个多项式的。如果能得到结果的算子,那么我们只要往算子里面填数据就可以得到所有的答案了。
 
 
寻找结果的多项式算子

首先第一步我们要确定多项式的系数,根据我们上面说的那样,这一题的答案一定是63*[polynomial],然后我们继续观察,根据拿两个剩一个这个条件,答案必须是mod 2 == 1,那么由于63中不含有2,那么[polynomial]必须形如2k+?(?是任意数),所以我们可以得到我们的答案一定是63*(2k + ?);同理,根据拿四个剩一个这个条件,答案必须是mod 4== 1,在前面的基础上,我们的答案必须形如63*(2(2k + j) + i) = 63*(4k + 2j +i)....
 
经过一系列分析,我们可以得到如下过程:
step1: 63k
step2: 63*(2k + i)
step3: 63*(2(2k + j) + i) 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值