LeetCode题解——最大公约数

题目描述:
如何求出最大公约数?

解法一:
暴力枚举法: 时间复杂度是O(min(a, b))。

1. public static int getGreatestCommonDivisor(int a, int b){
2. int big = a>b ? a:b;
3. int small = a<b ? a:b;
4. if(big%small == 0){
5. return small;
6. }
7. for(int i= small/2; i>1; i--){
8. if(small%i==0 && big%i==0){
9. return i;
10. }
11. }
12. return 1;
13. }
14.
14. public static void main(String[] args) {
15. System.out.println(getGreatestCommonDivisor(25, 5));
16. System.out.println(getGreatestCommonDivisor(100, 80));
17. System.out.println(getGreatestCommonDivisor(27, 14));
18. }

解法二:
辗转相除法: 时间复杂度不太好计算, 可以近似为O(log(max(a,b))), 但是取模运算性能较差。
首先, 计算出a除以b的余数c, 把问题转化成求b和c的最大公约数; 然后计算出b除以c的余数d, 把问题转化成求c和d的最大公约数; 再计算出c除以d的余数e, 把问题转化成求d和e的最大公约数……
以此类推, 逐渐把两个较大整数之间的运算简化成两个较小整数之间的运算, 直到两个数可以整除, 或者其中一个数减小到1为止。

1. public static int getGreatestCommonDivisorV2(int a, int b)
{ 
2. int big = a>b ? a:b;
2. int small = a<b ? a:b;
3. if(big%small == 0){
4. return small;
5. }
6. return getGreatestCommonDivisorV2(big%small, small);
7. }
8. 
9. public static void main(String[] args) {
10. System.out.println(getGreatestCommonDivisorV2(25, 5));
11. System.out.println(getGreatestCommonDivisorV2(100, 80));
12. System.out.println(getGreatestCommonDivisorV2(27, 14));
13. }

不过有一个问题, 当两个整数较大时, 做a%b取模运算的性能会比较差。

解法三:
更相减损术: 避免了取模运算, 但是算法性能不稳定, 最坏时间复杂度为O(max(a, b))。
它的原理更加简单: 两个正整数a和b(a>b) , 它们的最大公约数等于a-b的差值c和较小数b的最大公约数。 例如10和25, 25减10的差是15,那么10和25的最大公约数, 等同于10和15的最大公约数。

1. public static int getGreatestCommonDivisorV3(int a, int b)
2. {
. if(a == b){
3. return a;
4. }
5. int big = a>b ? a:b;
6. int small = a<b ? a:b;
7. return getGreatestCommonDivisorV3(big-small, small);
8. }
9.
10. public static void main(String[] args) {
11. System.out.println(getGreatestCommonDivisorV3(25, 5));
12. System.out.println(getGreatestCommonDivisorV3(100, 80));
13. System.out.println(getGreatestCommonDivisorV3(27, 14));
14. }

但是, 更相减损术依靠两数求差的方式来递归, 运算次数肯定远大于辗转相除法的取模方式吧?

解法四:
更相减损术与移位相结合: 不但避免了取模运算, 而且算法性能稳定, 时间复杂度为O(log(max(a, b)))。

移位运算的性能非常好。
(从下文开始, 获得最大公约数的方法getGreatestCommonDivisor被简写为gcd。 )
当a和b均为偶数时, gcd(a,b) = 2×gcd(a/2, b/2) = 2×gcd(a>>1,b>>1)。
当a为偶数, b为奇数时, gcd(a,b) = gcd(a/2,b) = gcd(a>>1,b)。
当a为奇数, b为偶数时, gcd(a,b) = gcd(a,b/2) = gcd(a,b>>1)。
当a和b均为奇数时, 先利用更相减损术运算一次, gcd(a,b) = gcd(b,ab), 此时a-b必然是偶数, 然后又可以继续进行移位运算。

1. public static int gcd(int a, int b){
2.     if(a == b){
3.        return a;
4.     }
5.     if((a&1)==0 && (b&1)==0){
6.         return gcd(a>>1, b>>1)<<1;
7.     } else if((a&1)==0 && (b&1)!=0){
8.          return gcd(a>>1, b);
9.     } else if((a&1)!=0 && (b&1)==0){
10.         return gcd(a, b>>1);
11.     } else {
12.         int big = a>b ? a:b;
13.         int small = a<b ? a:b;
14.         return gcd(big-small, small);
15.     }
16. }
17.
18. public static void main(String[] args) {
19.     System.out.println(gcd(25, 5));
20.     System.out.println(gcd(100, 80));
21.     System.out.println(gcd(27, 14));
22. }

相关说明:
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
LeetCode题解——最大公约数_第1张图片
假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

A = 0011 1100

B = 0000 1101

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A = 1100 0011

下表显示了 C++ 支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:
LeetCode题解——最大公约数_第2张图片

你可能感兴趣的:(C++)