这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战。
描述
如果有一个自然数 a 能被自然数 b 整除,则称 a 为 b 的倍数, b 为 a 的约数。几个自然数公有的约数,叫做这几个自然数的公约数。公约数中最大的一个公约数,称为这几个自然数的最大公约数。
输入 a 和 b , 请返回 a 和 b 的最大公约数。
数据范围:1≤ a,b ≤10^9
进阶:空间复杂度 O(1),时间复杂度 O(logn)。
暴力 for 循环
最简单的一种做法了,直接循环i++去找出两个数能够同时整除的数,然后一直循环到 i 等于最小的那个数。
/**
* 暴力 for 循环
* @param a
* @param b
* @return
*/
public int gcd (int a, int b) {
// write code here
int min=a>b?a:b;
int commonDivisor=1;
for (int i=1;i<=min;i++){
if (a%i==0&&b%i==0){
commonDivisor=i;
}
}
return commonDivisor;
}
上代码,十分简单粗暴是不?不过这可在牛客上不能通过。
当然我们今天不可能这么简单就结束了的。
改良
写完暴力 for 循环,我的第一个想法就是改良,在暴力循环的基础上改良一下,当然我也知道网上有很多非常好的方案,可能会用到各种公式,不过我还是想先看看自己能做到什么程度。
我觉得 1-100 中,最多的就是 2 的倍数,所以可以考虑先把 2 的倍数求出来,然后再去求两数的公约数,这样就能减少一定的运算时间。
public int gcd1 (int a, int b) {
int doubleCommonDivisor=1;
while (a%2==0&& b%2==0){
doubleCommonDivisor*=2;
a=a/2;
b=b/2;
}
int min=a>b?a:b;
int commonDivisor=1;
for (int i=1;i<=min;i++){
if (a%i==0&&b%i==0){
commonDivisor=i;
}
}
return commonDivisor*doubleCommonDivisor;
}
运行!
运行通过了,不过时间上比较长,内存上还不错。
除了 2 的数,基本上都不太好单独把倍数取出来,这种思路已经是穷途末路了。
辗转相除法
从大佬那里学了更加🐂的算法。
就是一直循环拿大的数去减小的数,然后拿差和小的数继续往下减,一直减到有一个数等于0,就说明最大公约数出来了。
比如说,10 和 30,第一遍 30-10=20;第二遍 20-10=10;第三遍 10-10=0;这时就得出了小的数 10 和差 0,这个小的数就是我们要的最大公约数。
如果是公约数为 1 的例子,17 和 30,第一遍 30-17=13;第二遍 17-13=4;第三遍 13-4=9;第四遍 9-4=5;第五遍 5-4=1;后面就是都减 1 了,知道有一个数等于 0,然后返回 1;
正反例都推理正确。
/**
* 辗转相减法
* @param a
* @param b
* @return
*/
public int gcd2 (int a, int b) {
if (b==0) {
//因为我们把小的数放在了后面,所以只需要看 b 是不是等于 0,当 b 等于 0 时,a
return a ;
}
return a>b?gcd2(b,a-b):gcd2(a,b-a);
}
运行!
运行时间快了好几十倍了都,占用内存也没多多少,看来暴力 for 循环是真滴辣鸡。
辗转相除法
比辗转相减法更牛皮的是辗转相除法,效率更快,更加稳定。
辗转相减法如果在极端情况下(也就是有一个差等于 1 的情况),还是很容易达到跟 for 循环一样的运行时间。
辗转相除法可以说跟辗转相减法一毛一样,就是把减法改成了求余。
就是一直循环拿大的数去除小的数,然后拿余数和小的数继续往下除,一直除到有一个数等于0,就说明最大公约数出来了。
继续用 10 和 30 的例子,第一遍 30%10=0;一遍结果就出来了,10。非常快速。
如果是公约数为 1 的例子,17 和 30,第一遍 30%17=13;第二遍 17%13=4;第三遍 13%4=1;第四遍 4%1=0;第五遍,有一个数等于 0,返回最大公约数 1。
正反两例都比辗转相减法的效率都高。
敲码!
/**
* 辗转相除法
* @param a
* @param b
* @return
*/
public int gcd3 (int a, int b) {
if (b==0) {
//因为我们把小的数放在了后面,所以只需要看 b 是不是等于 0,当 b 等于 0 时,a
return a ;
}
return a>b?gcd3(b,a%b):gcd3(a,b%a);
}
运行!
np!运行时间和占用内存没得说。
最后
感觉求最大公约数这道题对数学的要求比较高,仅靠自己的编码常识很难达到这道题的最优解。
总结出一个规律:
能不要遍历就不要遍历,能用乘除法就不要用加减法。
今天就到这里了。
这里是程序员徐小白,【每日算法】是我新开的一个专栏,在这里主要记录我学习算法的日常,也希望我能够坚持每日学习算法,不知道这样的文章风格您是否喜欢,不要吝啬您免费的赞,您的点赞、收藏以及评论都是我下班后坚持更文的动力。