一、最大公约数
方法:辗转相除法
设 a=50 ,b = 30 ,求(a,b)的最大公约数
过程:
1. 若b==0,则直接返回 a, 显然b !=0
2. 若b!=0,则 求(b,a%b)=(30,20)的最大公约数
3. 求(a%b,b%(a%b))=(20,10)的最大公约数
4. 同理,即求(10,0)的最大公约数,显然b=0,返回a,即10为最大公约数。
显然以上为一个递归过程。
1.求最大公约数
long gcd(long a,long b)
{
return b==0?a:gcd(b:a%b);
}
//求gcd(a,b) ,其中a>b,时间复杂度为O((log a)^3)
2.推得: 最小公倍数
long lcm(long a,long b)
{
return (long) a / gcd(a, b) * b;
}
3.原理证明:
// 证明辗转相除法就是证明如下关系:
gcd(a, b) = gcd(b, a % b)
假设a % b = r,即需要证明的关系为:gcd(a, b) = gcd(b, r)
证明过程:
因为a % b = r,所以如下两个等式必然成立
1) a = b * q + r,q为0、1、2、3....中的一个整数
2) r = a − b * q,q为0、1、2、3....中的一个整数
假设u是a和b的公因子,则有: a = s * u, b = t * u
把a和b带入2)得到,r = s * u - t * u * q = (s - t * q) * u
这说明 : u如果是a和b的公因子,那么u也是r的因子
假设v是b和r的公因子,则有: b = x * v, r = y * v
把b和r带入1)得到,a = x * v * q + y * v = (x * q + y) * v
这说明 : v如果是b和r的公因子,那么v也是a的公因子
综上,a和b的每一个公因子 也是 b和r的一个公因子,反之亦然
所以,a和b的全体公因子集合 = b和r的全体公因子集合
即gcd(a, b) = gcd(b, r)
证明结束
二、第 N 个神奇数字——最大公约数的经典应用
思路:
为了方便说明,假设 a=2 ,b =3 , 则从1开始逐渐增多,显然第一个神奇的数字为2 ,第二个神奇数字为3,第三个为4 。。。。。。
那么若要求第N个神奇的数字,
-
首先考虑能不能对N进行范围确定,使它不会超过这个范围呢?
可以,左区间很容易定,只需要为1即可,而右区间=N*a -
为什么右区间为N*a?
假设我们要求第100个神奇数,那么很明显1~200范围内肯定包含了第100个神奇数,因为可以被a=2整除的显然就是一半,更何况还有可以被b=3整除的,那么我们可以进一步得出: 越小的数,其越可能在某区间内存在越多的神奇的数。也就是
=》 右区间=N * min(a,b) -
如何确定第N个神奇数字的具体位置?
假设有函数 F(x),该函数可以求得 1~X范围内(包括X)有多少个神奇数字。那么很显然就是二分查找数字的过程。
=》从一个升序数组中,找到一个值为 F(x)=100 的数。 -
如何设计F(x)?
假设 x=200, 则在 1~200上有多少个1数可以被2和3整除?
显然 可以被 2整除数:200/2
可以被3整除数:200/3
显然,这之中重复计算了既可以被2整除也可以被3整除的数 :200/(23)
故F(200)=200/2+200/3-200/(23)
更一般的得到=》F(X)=X/a+X/b-X/(lcm(a,b))
long gcd(long a, long b)
{
return b == 0 ? a : gcd(b, a % b);
}
long lcm(long a, long b)
{
return (long)a / gcd(a, b) * b;
}
int getmin(int x,int y)
{
return x>y?y:x;
}
int nthMagicalNumber(int n, int a, int b)
{
long lcm_value = lcm(a, b);
long ans = 0;
// l = 0,二分左区间
// r = (long) n * getmin(a, b) ,二分右区间
// l......r
for (long l = 0, r = (long) n * getmin(a, b), m = 0; l <= r;)
{
m = (l + r) / 2; //得到中间位置
if (m / a + m / b - m / lcm_value >= n)
{
ans = m;
r = m - 1;
} else
{
l = m + 1;
}
}
return (int) (ans % 1000000007);
}
三. 同余原理
1. 背景:假设有一个所以元素都很大的数组int nums,要求将所以元素均相乘,并且返回真实答案%m的结果。更进一步的即当计算某个中间结果时,它的值远超过 int/long等类型的范围,如何进行这些大数的运算?
2.同余原理的用处:1、如何算对 真实值%m 2.多位 数运算
3.对于第二点的解释:
首先存在的误区:
对于多位数的数运算我用字符串拼凑最后直接取模不就行了,比如JAVA中的biginteger?
像Python中的int类型可以表示任意大小的整数,那么我直接用这个结果取模不就行了?
答案是:不可以!!!,因为如果位数真的过长,那么很可能算不完
4.分析:首先 一个 int (+)/(-)/(*)/(/) int,我们可以认为其运算的时间复杂度为O(1)。即使是long类型,我们也同样如此认为。其计算非常之快,为什么?因为这是固定位数。 但若在计算过程中将每一个中间结果均用真实值来保存并计算,显然这时候位数就不在固定,这就导致计算会越来越慢。实际上,当我们用K位的整数进行加/减,其时间复杂度为O(K),我们当然可以认为O(32),O(64)为O(1)的时间,但乘/除?时间复杂度为O(K^2),故当位数很大时,即使是一个很简单的运算,都可能导致超时!!!
5.解答:
[注意] 该过程需要保证不发生溢出,比如int*int,应该使用long或更大的类型来接住该数,之后在取模转化为整型。
[注意]该过程需要保证(a-b)%m=非负,因此要+m
在数学上,模是一个非负数的标量,它表示实数到零的距离。因此,模不会是负数。
除法同余需要用到求逆元,暂时跳过!!!
// 为了测试
public static long random() {
return (long) (Math.random() * Long.MAX_VALUE);
}
// 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果
public static int f1(long a, long b, long c, long d, int mod) {
BigInteger o1 = new BigInteger(String.valueOf(a)); // a
BigInteger o2 = new BigInteger(String.valueOf(b)); // b
BigInteger o3 = new BigInteger(String.valueOf(c)); // c
BigInteger o4 = new BigInteger(String.valueOf(d)); // d
BigInteger o5 = o1.add(o2); // a + b
BigInteger o6 = o3.subtract(o4); // c - d
BigInteger o7 = o1.multiply(o3); // a * c
BigInteger o8 = o2.multiply(o4); // b * d
BigInteger o9 = o5.multiply(o6); // (a + b) * (c - d)
BigInteger o10 = o7.subtract(o8); // (a * c - b * d)
BigInteger o11 = o9.add(o10); // ((a + b) * (c - d) + (a * c - b * d))
// ((a + b) * (c - d) + (a * c - b * d)) % mod
BigInteger o12 = o11.mod(new BigInteger(String.valueOf(mod)));
if (o12.signum() == -1) {
// 如果是负数那么+mod返回
return o12.add(new BigInteger(String.valueOf(mod))).intValue();
} else {
// 如果不是负数直接返回
return o12.intValue();
}
}
// 计算 ((a + b) * (c - d) + (a * c - b * d)) % mod 的非负结果
public static int f2(long a, long b, long c, long d, int mod) {
int o1 = (int) (a % mod); // a
int o2 = (int) (b % mod); // b
int o3 = (int) (c % mod); // c
int o4 = (int) (d % mod); // d
int o5 = (o1 + o2) % mod; // a + b
int o6 = (o3 - o4 + mod) % mod; // c - d
int o7 = (int) (((long) o1 * o3) % mod); // a * c
int o8 = (int) (((long) o2 * o4) % mod); // b * d
int o9 = (int) (((long) o5 * o6) % mod); // (a + b) * (c - d)
int o10 = (o7 - o8 + mod) % mod; // (a * c - b * d)
int ans = (o9 + o10) % mod; // ((a + b) * (c - d) + (a * c - b * d)) % mod
return ans;
}
public static void main(String[] args) {
System.out.println("测试开始");
int testTime = 100000;
int mod = 1000000007;
for (int i = 0; i < testTime; i++) {
long a = random();
long b = random();
long c = random();
long d = random();
if (f1(a, b, c, d, mod) != f2(a, b, c, d, mod)) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
System.out.println("===");
long a = random();
long b = random();
long c = random();
long d = random();
System.out.println("a : " + a);
System.out.println("b : " + b);
System.out.println("c : " + c);
System.out.println("d : " + d);
System.out.println("===");
System.out.println("f1 : " + f1(a, b, c, d, mod));
System.out.println("f2 : " + f2(a, b, c, d, mod));
}