数论问题
1、完全数
1.1、什么是完全数
完全数(Perfect number)是一些特殊的自然整数。完全数等于其所有因子的和。这里所谓的因子是指所有可以整除这个数的数,而不包括该数本身。
其实谈到完全数,与之相关的两个概念便是亏数和盈数。一般来说,通过其所有真因子的和来判断一个自然数是亏数、盈数及完全数。
- 当一个自然数的所有真因子的和小于该自然数,那么该自然数便是亏数。
- 当一个自然数的所有真因子的和大于该自然数,那么该自然数便是盈数。
- 当一个自然数的所有真因子的和等于该自然数,那么该自然数便是完全数。
例如,4的所有真因子包括1和2,而4>1+2,所以4是一个亏数;6的所有真因子包括1、2、3,而6=1+2+3,因此6是一个完全数;12的所有真因子包括1、2、3、4、6,而12<1+2+3+4+6,所以12是一个盈数。
下面举几个典型的完全数的例子:
- 6=1+2+3
- 28=1+2+4+7+14
- 496=1+2+4+8+16+31+62+124+248
- 8128=1+2+4+8+16+32+64+127+254+508+1016+2032+4064
完全数的特殊性质:
每一个完全数都可以表示成连续自然数的和
每一个完全数都可以表示成连续自然数的和,这些自然数并不一定是完全数的因数。例如:
6=1+2+3
28=1+2+3+4+5+6+7
496=1+2+3+4+...+29+30+31每一个完全数都是调和数
如果一个正整数的所有因子的调和平均是整数,那么这个正整数便是调和数。而每一个完全数都是调和数,例如:
对于完全数6来说,1/1+1/2+1/3+1/6=2
对于完全数28来说,1/1+1/2+1/4+1/7+1/14+1/28=2每一个完全数都可以表示为2的一些连续正整数次幂之和
每一个完全数都可以表示为2的一些连续正整数次幂之和例如:
6=21+22
28=2^2+2N3+2N4
8128=26+27+28+29+210+211+2^12已知的完全数都是以6或者8结尾
已知的完全数都是以6或者8结尾,例如,6、28、496、8128、33550336等。从这里也可以看出,已知的每一个完全数都是偶数,但还没有严格证明没有奇数的完全数。除6之外的完全数都可以表示成连续奇立方之和
除6之外的完全数都可以表示成连续奇立方之和,例如:
28=1A3+3^3
496=1A3+33+53+7^3
8128=1^3+33+53++153
1.2、计算完全数的算法
java
/**
* @Author: acton_zhang
* @Date: 2026/1/9 下午2:56
* @Version 1.0
* 计算完全平方数
*/
public class PerfectNumberCalculator {
/**
* 方法1:暴力法 - 检查一个数是否为完全数
* 时间复杂度:O(n)
* 适用于小数字
*/
public static boolean isPerfectNumberBruteForce(int n) {
if (n < 2)
return false;//完全平方数至少是6
int sum = 1;//1是任何正整数的因子
int limit = (int)Math.sqrt(n);//求平方根
for (int i = 2; i <= limit; i++) {
if (n % i == 0) {//能整除即为因子
sum += i;
int other = n / i;//另一个因子
if (other != i) {//避免重复添加因子
sum += other;
}
}
}
return sum == n;
}
/**
* 方法2:欧几里得-欧拉定理法(只适用于偶完全数)
* 时间复杂度:取决于梅森素数的检测
*/
public static boolean isPerfectEuclidEuler(long n) {
// 检查是否满足形式 2^(p-1) * (2^p - 1)
for (int p = 2; p <= 31; p++) { // 限制p以防止溢出
long mersenne = (1L << p) - 1; // 2^p - 1
if (isPrime(mersenne)) { // 需要是梅森素数
long perfect = (1L << (p - 1)) * mersenne; // 2^(p-1) * (2^p - 1)
if (perfect == n) {
return true;
}
if (perfect > n) {
break; // 由于完美数递增,可以提前结束
}
}
}
return false;
}
/**
* 判断一个数是否为素数
*/
private static boolean isPrime(long n) {
if (n < 2)
return false;
if (n == 2 || n == 3)
return true;
if (n % 2 == 0 || n % 3 == 0)
return true;
long limit = (long)Math.sqrt(n) + 1;
for (long i = 5; i <= limit; i+= 5) {
if (n % i == 0|| n % (i + 2) == 0) {
return false;
}
}
return true;
}
/**
* 获取一个数的所有真因子(不包括自身)
*/
public static List<Integer> getProperDivisors(int n) {
List<Integer> divisors = new ArrayList<>();
if (n < 1) return divisors;
divisors.add(1);
int limit = (int) Math.sqrt(n);
for (int i = 2; i <= limit; i++) {
if (n % i == 0) {
divisors.add(i);
int other = n / i;
if (other != i && other != n) { // 避免添加自身
divisors.add(other);
}
}
}
divisors.sort(Integer::compareTo);
return divisors;
}
/**
* 查找指定范围内的所有完全数
*/
public static List<Integer> findPerfectNumbersInRange(int start, int end) {
List<Integer> perfectNumbers = new ArrayList<>();
for (int n = Math.max(start, 2); n <= end; n++) {
if (isPerfectNumberBruteForce(n)) {
perfectNumbers.add(n);
}
}
return perfectNumbers;
}
}
2、亲密数
2.1、什么是亲密数
如果整数a的因子和等于整数b,整数b的因子和等于整数a,因子包括1但不包括本身,且a不等于b,则称a、b为亲密数。
例如,220和284便是一对亲密数,满足如下规则。
- 220的各个因子之和为1+2+4+5+10+11+20+22+44+55+110=284;
284的各个因子之和为:1+2+4+71+142=220。 - 另外,1184和1210是一对亲密数,因为其满足如下规则:1184的各个因子之和为1+2+4+8+16+32+37+74+148+296+592=1210;
1210的各个因子之和为:1+2+5+10+11+22+55+110+121+242+605=1184。
2.2、计算亲密数的算法
java
/**
* @Author: acton_zhang
* @Date: 2026/1/9 下午10:08
* @Version 1.0
*/
public class AmicableNumbersCalculator {
/**
* 方法1:计算一个数的真因子之和
* 时间复杂度:O(√n)
*/
public static int sumOfProperDivisors(int n) {
if (n <= 1) return 0;
int sum = 1; // 1总是真因子
int limit = (int) Math.sqrt(n);
for (int i = 2; i <= limit; i++) {
if (n % i == 0) {
sum += i;
int other = n / i;
if (other != i && other != n) { // 避免添加n本身
sum += other;
}
}
}
return sum;
}
/**
* 方法2:检查两个数是否为亲密数
*/
public static boolean areAmicable(int a, int b) {
if (a == b) return false; // 亲密数必须是不同的数
int sumA = sumOfProperDivisors(a);
int sumB = sumOfProperDivisors(b);
return sumA == b && sumB == a;
}
/**
* 方法3:查找指定范围内的所有亲密数对
* 时间复杂度:O(n√n)
*/
public static List<int[]> findAmicablePairsInRange(int start, int end) {
List<int[]> pairs = new ArrayList<>();
for (int a = Math.max(start, 2); a <= end; a++) {
int b = sumOfProperDivisors(a);
// 检查条件:
// 1. b 在范围内
// 2. b > a (避免重复)
// 3. a 和 b 是亲密数
if (b > a && b <= end) {
int sumB = sumOfProperDivisors(b);
if (sumB == a) {
pairs.add(new int[]{a, b});
}
}
}
return pairs;
}
/**
* 方法4:优化的查找算法(缓存因子和)
*/
public static List<int[]> findAmicablePairsOptimized(int limit) {
List<int[]> pairs = new ArrayList<>();
int[] divisorSums = new int[limit + 1];
// 预计算所有数的因子和
for (int i = 1; i <= limit; i++) {
divisorSums[i] = sumOfProperDivisors(i);
}
// 查找亲密数对
for (int a = 2; a <= limit; a++) {
int b = divisorSums[a];
if (b > limit) continue; // b超出范围
if (b <= a) continue; // 避免重复
if (divisorSums[b] == a) {
pairs.add(new int[]{a, b});
}
}
return pairs;
}
/**
* 方法5:获取一个数的所有真因子
*/
public static List<Integer> getProperDivisors(int n) {
List<Integer> divisors = new ArrayList<>();
if (n <= 1) return divisors;
divisors.add(1);
int limit = (int) Math.sqrt(n);
for (int i = 2; i <= limit; i++) {
if (n % i == 0) {
divisors.add(i);
int other = n / i;
if (other != i && other != n) {
divisors.add(other);
}
}
}
divisors.sort(Integer::compareTo);
return divisors;
}
/**
* 方法6:检查一个数是否在已知的亲密数对中
*/
public static boolean isInAmicablePair(int n) {
int sum = sumOfProperDivisors(n);
if (sum == n) return false; // 完全数不是亲密数
int sumOfSum = sumOfProperDivisors(sum);
return sumOfSum == n && sum != n;
}
/**
* 获取一个数的"伴侣"(如果存在)
*/
public static Integer getAmicablePartner(int n) {
int sum = sumOfProperDivisors(n);
if (sum == n) return null; // 完全数
int sumOfSum = sumOfProperDivisors(sum);
if (sumOfSum == n && sum != n) {
return sum;
}
return null;
}
public static void main(String[] args) {
System.out.println("========== 亲密数计算器 ==========\n");
// 1. 测试已知的亲密数对
System.out.println("1. 已知亲密数对测试:");
int[][] knownPairs = {
{220, 284},
{1184, 1210},
{2620, 2924},
{5020, 5564},
{6232, 6368},
{10744, 10856},
{12285, 14595},
{17296, 18416}
};
for (int[] pair : knownPairs) {
int a = pair[0];
int b = pair[1];
boolean areAmicable = areAmicable(a, b);
List<Integer> divisorsA = getProperDivisors(a);
List<Integer> divisorsB = getProperDivisors(b);
int sumA = divisorsA.stream().mapToInt(Integer::intValue).sum();
int sumB = divisorsB.stream().mapToInt(Integer::intValue).sum();
System.out.printf("(%d, %d): %s%n", a, b, areAmicable ? "是亲密数" : "不是亲密数");
System.out.printf(" d(%d) = %s = %d%n", a, divisorsA, sumA);
System.out.printf(" d(%d) = %s = %d%n", b, divisorsB, sumB);
System.out.println();
}
// 2. 查找范围内的亲密数
System.out.println("2. 查找1-10000范围内的亲密数对:");
List<int[]> pairs1 = findAmicablePairsInRange(1, 10000);
for (int[] pair : pairs1) {
System.out.printf(" (%d, %d)%n", pair[0], pair[1]);
}
// 3. 优化算法测试
System.out.println("\n3. 优化算法测试 (1-20000):");
List<int[]> pairs2 = findAmicablePairsOptimized(20000);
for (int[] pair : pairs2) {
System.out.printf(" (%d, %d)%n", pair[0], pair[1]);
}
// 4. 检查单个数字
System.out.println("\n4. 检查单个数字:");
int[] testNumbers = {220, 284, 1184, 1210, 496, 28, 12};
for (int n : testNumbers) {
Integer partner = getAmicablePartner(n);
if (partner != null) {
System.out.printf(" %d 的亲密伴侣是 %d%n", n, partner);
} else {
if (sumOfProperDivisors(n) == n) {
System.out.printf(" %d 是完全数,没有亲密伴侣%n", n);
} else {
System.out.printf(" %d 没有亲密伴侣%n", n);
}
}
}
}
}
java
========== 亲密数计算器 ==========
1. 已知亲密数对测试:
(220, 284): 是亲密数
d(220) = [1, 2, 4, 5, 10, 11, 20, 22, 44, 55, 110] = 284
d(284) = [1, 2, 4, 71, 142] = 220
(1184, 1210): 是亲密数
d(1184) = [1, 2, 4, 8, 16, 32, 37, 74, 148, 296, 592] = 1210
d(1210) = [1, 2, 5, 10, 11, 22, 55, 110, 121, 242, 605] = 1184
(2620, 2924): 是亲密数
d(2620) = [1, 2, 4, 5, 10, 20, 131, 262, 524, 655, 1310] = 2924
d(2924) = [1, 2, 4, 17, 34, 43, 68, 86, 172, 731, 1462] = 2620
(5020, 5564): 是亲密数
d(5020) = [1, 2, 4, 5, 10, 20, 251, 502, 1004, 1255, 2510] = 5564
d(5564) = [1, 2, 4, 13, 26, 52, 107, 214, 428, 1391, 2782] = 5020
(6232, 6368): 是亲密数
d(6232) = [1, 2, 4, 8, 19, 38, 41, 76, 82, 152, 164, 328, 779, 1558, 3116] = 6368
d(6368) = [1, 2, 4, 8, 16, 32, 199, 398, 796, 1592, 3184] = 6232
(10744, 10856): 是亲密数
d(10744) = [1, 2, 4, 8, 17, 34, 68, 79, 136, 158, 316, 632, 1343, 2686, 5372] = 10856
d(10856) = [1, 2, 4, 8, 23, 46, 59, 92, 118, 184, 236, 472, 1357, 2714, 5428] = 10744
(12285, 14595): 是亲密数
d(12285) = [1, 3, 5, 7, 9, 13, 15, 21, 27, 35, 39, 45, 63, 65, 91, 105, 117, 135, 189, 195, 273, 315, 351, 455, 585, 819, 945, 1365, 1755, 2457, 4095] = 14595
d(14595) = [1, 3, 5, 7, 15, 21, 35, 105, 139, 417, 695, 973, 2085, 2919, 4865] = 12285
(17296, 18416): 是亲密数
d(17296) = [1, 2, 4, 8, 16, 23, 46, 47, 92, 94, 184, 188, 368, 376, 752, 1081, 2162, 4324, 8648] = 18416
d(18416) = [1, 2, 4, 8, 16, 1151, 2302, 4604, 9208] = 17296
2. 查找1-10000范围内的亲密数对:
(220, 284)
(1184, 1210)
(2620, 2924)
(5020, 5564)
(6232, 6368)
3. 优化算法测试 (1-20000):
(220, 284)
(1184, 1210)
(2620, 2924)
(5020, 5564)
(6232, 6368)
(10744, 10856)
(12285, 14595)
(17296, 18416)
4. 检查单个数字:
220 的亲密伴侣是 284
284 的亲密伴侣是 220
1184 的亲密伴侣是 1210
1210 的亲密伴侣是 1184
496 是完全数,没有亲密伴侣
28 是完全数,没有亲密伴侣
12 没有亲密伴侣
3、水仙花数
3.1、什么是水仙花数
水仙花数是指一个n位正整数(n≥3),它的每个位上的数字的n次幂之和等于它本身。水仙花数也是一种具有特殊性质的数。
常见类型:
- 三位水仙花数:153 = 1³ + 5³ + 3³
- 四位水仙花数:1634 = 1⁴ + 6⁴ + 3⁴ + 4⁴
- 阿姆斯特朗数:水仙花数的别称
- 自幂数:水仙花数的推广
已知水仙花数:
- 1位:1, 2, 3, 4, 5, 6, 7, 8, 9
- 3位:153, 370, 371, 407
- 4位:1634, 8208, 9474
- 5位:54748, 92727, 93084
- 6位:548834
- 7位:1741725, 4210818, 9800817, 9926315
- 8位:24678050, 24678051, 88593477
- 最高位:有39位的水仙花数
3.2、计算水仙花数的算法
java
/**
* @Author: acton_zhang
* @Date: 2026/1/9 下午10:31
* @Version 1.0
*/
public class NarcissisticNumberCalculator {
/**
* 方法1:判断一个数是否为水仙花数
* 时间复杂度:O(n) 其中n是数字位数
*/
public static boolean isNarcissisticNumber(int number) {
if (number < 0) return false;
int original = number;
int sum = 0;
int digits = countDigits(number);
while (number > 0) {
int digit = number % 10;
sum += Math.pow(digit, digits);
number /= 10;
}
return sum == original;
}
/**
* 方法2:计算数字的位数
*/
public static int countDigits(int number) {
if (number == 0) return 1;
return (int) Math.log10(Math.abs(number)) + 1;
}
/**
* 方法3:查找指定位数的所有水仙花数
*/
public static List<Integer> findNarcissisticNumbersByDigits(int digits) {
List<Integer> results = new ArrayList<>();
int start = (int) Math.pow(10, digits - 1);
int end = (int) Math.pow(10, digits) - 1;
// 对于1位数,0-9也需要检查
if (digits == 1) {
start = 0;
}
for (int i = start; i <= end; i++) {
if (isNarcissisticNumber(i)) {
results.add(i);
}
}
return results;
}
/**
* 方法4:查找指定范围内的水仙花数
*/
public static List<Integer> findNarcissisticNumbersInRange(int start, int end) {
List<Integer> results = new ArrayList<>();
for (int i = start; i <= end; i++) {
if (isNarcissisticNumber(i)) {
results.add(i);
}
}
return results;
}
/**
* 方法5:优化的水仙花数检查(缓存幂次)
*/
public static boolean isNarcissisticOptimized(int number) {
if (number < 0) return false;
int original = number;
int sum = 0;
int digits = countDigits(number);
// 缓存0-9的digits次幂
int[] powers = new int[10];
for (int i = 0; i <= 9; i++) {
powers[i] = (int) Math.pow(i, digits);
}
while (number > 0) {
int digit = number % 10;
sum += powers[digit];
number /= 10;
}
return sum == original;
}
/**
* 方法6:获取数字的各位数字
*/
public static List<Integer> getDigits(int number) {
List<Integer> digits = new ArrayList<>();
if (number == 0) {
digits.add(0);
return digits;
}
number = Math.abs(number);
while (number > 0) {
digits.add(0, number % 10); // 添加到开头保持顺序
number /= 10;
}
return digits;
}
/**
* 方法7:验证水仙花数并显示计算过程
*/
public static String verifyNarcissisticNumber(int number) {
if (!isNarcissisticNumber(number)) {
return number + " 不是水仙花数";
}
List<Integer> digits = getDigits(number);
int digitCount = digits.size();
StringBuilder process = new StringBuilder();
process.append(number).append(" = ");
for (int i = 0; i < digits.size(); i++) {
int digit = digits.get(i);
process.append(digit).append("^").append(digitCount);
if (i < digits.size() - 1) {
process.append(" + ");
}
}
process.append(" = ");
int sum = 0;
for (int i = 0; i < digits.size(); i++) {
int digit = digits.get(i);
int power = (int) Math.pow(digit, digitCount);
sum += power;
process.append(power);
if (i < digits.size() - 1) {
process.append(" + ");
}
}
process.append(" = ").append(sum);
return process.toString();
}
public static void main(String[] args) {
System.out.println("========== 水仙花数计算器 ==========\n");
// 1. 测试已知的水仙花数
System.out.println("1. 已知水仙花数验证:");
int[] testNumbers = {153, 370, 371, 407, 1634, 8208, 9474, 54748, 123, 100};
for (int num : testNumbers) {
boolean isNarc = isNarcissisticNumber(num);
System.out.printf("%-6d: %s", num, isNarc ? "是水仙花数" : "不是水仙花数");
if (isNarc) {
System.out.printf(" (验证: %s)", verifyNarcissisticNumber(num));
}
System.out.println();
}
// 2. 查找指定位数的水仙花数
System.out.println("\n2. 查找指定位数的水仙花数:");
for (int digits = 1; digits <= 7; digits++) {
List<Integer> numbers = findNarcissisticNumbersByDigits(digits);
if (!numbers.isEmpty()) {
System.out.printf("%d 位数 (%d 个): %s%n",
digits, numbers.size(), numbers);
} else {
System.out.printf("%d 位数: 无%n", digits);
}
}
// 3. 查找范围内的水仙花数
System.out.println("\n3. 查找范围内的水仙花数:");
int[][] ranges = {
{1, 1000},
{1000, 10000},
{1, 100000}
};
for (int[] range : ranges) {
List<Integer> numbers = findNarcissisticNumbersInRange(range[0], range[1]);
System.out.printf("范围 [%d, %d]: %s%n",
range[0], range[1], numbers);
}
// 4. 优化算法测试
System.out.println("\n4. 优化算法测试:");
long start = System.currentTimeMillis();
List<Integer> allNarcissistic = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
if (isNarcissisticOptimized(i)) {
allNarcissistic.add(i);
}
}
long end = System.currentTimeMillis();
System.out.printf("1-1000000 内的水仙花数 (%d 个): %s%n",
allNarcissistic.size(), allNarcissistic);
System.out.printf("优化算法耗时: %d ms%n", end - start);
}
}
java
========== 水仙花数计算器 ==========
1. 已知水仙花数验证:
153 : 是水仙花数 (验证: 153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153)
370 : 是水仙花数 (验证: 370 = 3^3 + 7^3 + 0^3 = 27 + 343 + 0 = 370)
371 : 是水仙花数 (验证: 371 = 3^3 + 7^3 + 1^3 = 27 + 343 + 1 = 371)
407 : 是水仙花数 (验证: 407 = 4^3 + 0^3 + 7^3 = 64 + 0 + 343 = 407)
1634 : 是水仙花数 (验证: 1634 = 1^4 + 6^4 + 3^4 + 4^4 = 1 + 1296 + 81 + 256 = 1634)
8208 : 是水仙花数 (验证: 8208 = 8^4 + 2^4 + 0^4 + 8^4 = 4096 + 16 + 0 + 4096 = 8208)
9474 : 是水仙花数 (验证: 9474 = 9^4 + 4^4 + 7^4 + 4^4 = 6561 + 256 + 2401 + 256 = 9474)
54748 : 是水仙花数 (验证: 54748 = 5^5 + 4^5 + 7^5 + 4^5 + 8^5 = 3125 + 1024 + 16807 + 1024 + 32768 = 54748)
123 : 不是水仙花数
100 : 不是水仙花数
2. 查找指定位数的水仙花数:
1 位数 (10 个): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2 位数: 无
3 位数 (4 个): [153, 370, 371, 407]
4 位数 (3 个): [1634, 8208, 9474]
5 位数 (3 个): [54748, 92727, 93084]
6 位数 (1 个): [548834]
7 位数 (4 个): [1741725, 4210818, 9800817, 9926315]
3. 查找范围内的水仙花数:
范围 [1, 1000]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407]
范围 [1000, 10000]: [1634, 8208, 9474]
范围 [1, 100000]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474, 54748, 92727, 93084]
4. 优化算法测试:
1-1000000 内的水仙花数 (20 个): [1, 2, 3, 4, 5, 6, 7, 8, 9, 153, 370, 371, 407, 1634, 8208, 9474, 54748, 92727, 93084, 548834]
优化算法耗时: 1183 ms
4、自守数
4.1、什么是自守数
自守数(Automorphic Number):一个正整数的平方的尾数等于该数本身。
数学定义:
- 对于 n 位数 k,如果 k² 的最后 n 位等于 k,则 k 是自守数
- 公式:k² ≡ k (mod 10ⁿ),其中 n 是 k 的位数
数学性质:
- 自守数总是以 5 或 6 结尾
- 自守数的平方以 25 或 76 结尾
- 有无限多个自守数
- 自守数序列:5, 6, 25, 76, 376, 625, 9376, 90625, 109376, 890625, ...
特殊类型:
- 纯自守数:平方的后缀正好是原数
- 循环自守数:更高次幂也保持自守性
4.2、计算自守数的算法
java
import java.math.BigInteger;
import java.util.*;
/**
* 自守数计算器
*/
public class AutomorphicNumberCalculator {
/**
* 方法1:判断一个数是否为自守数
* 时间复杂度:O(1)
*/
public static boolean isAutomorphicNumber(int number) {
if (number < 0) return false;
long square = (long) number * number;
int digits = countDigits(number);
// 取平方的最后digits位
long lastDigits = square % (long) Math.pow(10, digits);
return lastDigits == number;
}
/**
* 方法2:判断大数是否为自守数
*/
public static boolean isAutomorphicBigInteger(BigInteger number) {
if (number.compareTo(BigInteger.ZERO) < 0) {
return false;
}
BigInteger square = number.multiply(number);
int digits = countDigitsBigInteger(number);
// 取平方的最后digits位
BigInteger modulus = BigInteger.TEN.pow(digits);
BigInteger lastDigits = square.mod(modulus);
return lastDigits.equals(number);
}
/**
* 方法3:计算数字的位数
*/
public static int countDigits(int number) {
if (number == 0) return 1;
return (int) Math.log10(Math.abs(number)) + 1;
}
public static int countDigitsBigInteger(BigInteger number) {
if (number.equals(BigInteger.ZERO)) {
return 1;
}
return number.toString().length();
}
/**
* 方法4:查找指定位数的所有自守数
*/
public static List<Integer> findAutomorphicNumbersByDigits(int digits) {
List<Integer> results = new ArrayList<>();
int start = (int) Math.pow(10, digits - 1);
int end = (int) Math.pow(10, digits) - 1;
for (int i = start; i <= end; i++) {
if (isAutomorphicNumber(i)) {
results.add(i);
}
}
return results;
}
/**
* 方法5:查找指定范围内的自守数
*/
public static List<Integer> findAutomorphicNumbersInRange(int start, int end) {
List<Integer> results = new ArrayList<>();
for (int i = start; i <= end; i++) {
if (isAutomorphicNumber(i)) {
results.add(i);
}
}
return results;
}
/**
* 方法6:优化的自守数检查
*/
public static boolean isAutomorphicOptimized(int number) {
if (number < 0) return false;
// 快速检查:自守数必须以5或6结尾
int lastDigit = number % 10;
if (!(lastDigit == 5 || lastDigit == 6)) {
return false;
}
return isAutomorphicNumber(number);
}
/**
* 方法7:生成自守数的平方验证字符串
*/
public static String verifyAutomorphicNumber(int number) {
if (!isAutomorphicNumber(number)) {
return number + " 不是自守数";
}
long square = (long) number * number;
int digits = countDigits(number);
StringBuilder sb = new StringBuilder();
sb.append(number).append("² = ").append(square).append("\n");
sb.append("平方的后").append(digits).append("位: ");
long lastDigits = square % (long) Math.pow(10, digits);
sb.append(lastDigits).append(" = ").append(number);
return sb.toString();
}
/**
* 方法8:查找下一个自守数
*/
public static int findNextAutomorphicNumber(int start) {
for (int i = start + 1; i <= Integer.MAX_VALUE; i++) {
if (isAutomorphicNumber(i)) {
return i;
}
}
return -1; // 未找到
}
/**
* 方法9:查找前n个自守数
*/
public static List<Integer> findFirstNAutomorphicNumbers(int n) {
List<Integer> results = new ArrayList<>();
int count = 0;
int current = 0;
while (count < n) {
if (isAutomorphicNumber(current)) {
results.add(current);
count++;
}
current++;
}
return results;
}
public static void main(String[] args) {
System.out.println("========== 自守数计算器 ==========\n");
// 1. 测试已知的自守数
System.out.println("1. 已知自守数验证:");
int[] testNumbers = {5, 6, 25, 76, 376, 625, 9376, 90625, 109376, 12, 20};
for (int num : testNumbers) {
boolean isAuto = isAutomorphicNumber(num);
System.out.printf("%-8d: %s", num, isAuto ? "是自守数" : "不是自守数");
if (isAuto) {
System.out.printf(" (验证: %s)", verifyAutomorphicNumber(num));
}
System.out.println();
}
// 2. 查找指定位数的自守数
System.out.println("\n2. 查找指定位数的自守数:");
for (int digits = 1; digits <= 7; digits++) {
List<Integer> numbers = findAutomorphicNumbersByDigits(digits);
if (!numbers.isEmpty()) {
System.out.printf("%d 位数 (%d 个): %s%n",
digits, numbers.size(), numbers);
} else {
System.out.printf("%d 位数: 无%n", digits);
}
}
// 3. 查找范围内的自守数
System.out.println("\n3. 查找范围内的自守数:");
int[][] ranges = {
{1, 100},
{100, 1000},
{1000, 10000},
{1, 1000000}
};
for (int[] range : ranges) {
List<Integer> numbers = findAutomorphicNumbersInRange(range[0], range[1]);
System.out.printf("范围 [%d, %d]: %s%n",
range[0], range[1], numbers);
}
// 4. 优化算法测试
System.out.println("\n4. 优化算法测试:");
long start = System.currentTimeMillis();
List<Integer> allAutomorphic = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
if (isAutomorphicOptimized(i)) {
allAutomorphic.add(i);
}
}
long end = System.currentTimeMillis();
System.out.printf("1-1000000 内的自守数 (%d 个): %s%n",
allAutomorphic.size(), allAutomorphic);
System.out.printf("优化算法耗时: %d ms%n", end - start);
// 5. 查找前N个自守数
System.out.println("\n5. 前20个自守数:");
List<Integer> first20 = findFirstNAutomorphicNumbers(20);
for (int i = 0; i < first20.size(); i++) {
int num = first20.get(i);
long square = (long) num * num;
System.out.printf("%2d. %-8d (平方: %d)%n", i+1, num, square);
}
}
}
5、最大公约数
5.1、什么是最大公约数
最大公约数和最小公倍数应用广泛。如果有一个自然数a能被自然数b整除,则称a为b的倍数,b为a的约数。几个自然数公有的约数,叫做这几个自然数的公约数。公约数中最大的一个,一般就称为这几个自然数的最大公约数(greatest common divisor,缩写为gcd)。
例如,在自然数4、8、12中,1、2和4是这几个自然数的公约数,而4则是最大公约数。
5.2、辗转相除法
欧几里得的辗转相除算法是计算两个自然数最大公约数的传统算法,对于多个自然数可以执行多次辗转相除法来得到最大公约数。辗转相除法的执行过程如下:
- 对于已知两自然数m、n,假设m>n;
- 计算m除以n,将得到的余数记为r:
- 如果=0,则n为求得的最大公约数,否则执行下面一步:
- 将n的值保存到m中,将r的值保存到n中,重复执行步骤(2)和(3),直到r=0,便得到最大公约数。
java
import java.math.BigInteger;
import java.util.*;
/**
* 辗转相除法计算最大公约数
*/
public class EuclideanAlgorithm {
/**
* 方法1:递归实现辗转相除法
* 时间复杂度:O(log(min(a, b)))
*/
public static int gcdRecursive(int a, int b) {
// 处理负数
a = Math.abs(a);
b = Math.abs(b);
if (b == 0) {
return a;
}
return gcdRecursive(b, a % b);
}
/**
* 方法2:迭代实现辗转相除法
*/
public static int gcdIterative(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
/**
* 方法3:更安全的迭代实现(处理边界情况)
*/
public static int gcdSafe(int a, int b) {
if (a == 0 && b == 0) {
return 0; // 定义gcd(0,0)=0
}
a = Math.abs(a);
b = Math.abs(b);
// 处理特殊情况
if (a == 0) return b;
if (b == 0) return a;
// 确保a >= b
if (a < b) {
int temp = a;
a = b;
b = temp;
}
while (b != 0) {
int remainder = a % b;
a = b;
b = remainder;
}
return a;
}
/**
* 方法4:计算最小公倍数(LCM)
* 公式:lcm(a, b) = |a * b| / gcd(a, b)
*/
public static int lcm(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
int gcd = gcdSafe(a, b);
return Math.abs(a / gcd * b); // 先除后乘避免溢出
}
/**
* 方法5:计算多个数的最大公约数
*/
public static int gcdMultiple(int... numbers) {
if (numbers == null || numbers.length == 0) {
return 0;
}
int result = numbers[0];
for (int i = 1; i < numbers.length; i++) {
result = gcdSafe(result, numbers[i]);
if (result == 1) {
break; // 提前终止
}
}
return result;
}
/**
* 方法6:计算多个数的最小公倍数
*/
public static int lcmMultiple(int... numbers) {
if (numbers == null || numbers.length == 0) {
return 0;
}
int result = numbers[0];
for (int i = 1; i < numbers.length; i++) {
result = lcm(result, numbers[i]);
}
return result;
}
/**
* 方法7:扩展欧几里得算法
* 返回数组 [gcd, x, y] 满足 ax + by = gcd
*/
public static int[] extendedGcd(int a, int b) {
if (b == 0) {
return new int[]{a, 1, 0};
}
int[] result = extendedGcd(b, a % b);
int gcd = result[0];
int x1 = result[1];
int y1 = result[2];
int x = y1;
int y = x1 - (a / b) * y1;
return new int[]{gcd, x, y};
}
/**
* 方法8:二进制GCD算法(更高效)
*/
public static int binaryGcd(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
if (a == 0) return b;
if (b == 0) return a;
// 提取公因子2
int shift = 0;
while (((a | b) & 1) == 0) { // 都是偶数
a >>= 1;
b >>= 1;
shift++;
}
while ((a & 1) == 0) { // a是偶数
a >>= 1;
}
// 现在a是奇数
do {
while ((b & 1) == 0) { // b是偶数
b >>= 1;
}
if (a > b) {
int temp = a;
a = b;
b = temp;
}
b = b - a;
} while (b != 0);
return a << shift; // 恢复因子2
}
/**
* 方法9:使用BigInteger计算大数GCD
*/
public static BigInteger gcdBigInteger(BigInteger a, BigInteger b) {
return a.gcd(b);
}
/**
* 方法10:验证贝祖等式 ax + by = gcd(a, b)
*/
public static boolean verifyBezout(int a, int b) {
int[] result = extendedGcd(a, b);
int gcd = result[0];
int x = result[1];
int y = result[2];
int leftSide = a * x + b * y;
return leftSide == gcd;
}
public static void main(String[] args) {
System.out.println("========== 辗转相除法计算最大公约数 ==========\n");
// 1. 测试基本GCD计算
System.out.println("1. 基本GCD计算:");
int[][] testPairs = {
{48, 18},
{56, 98},
{101, 103},
{0, 5},
{5, 0},
{0, 0},
{-48, 18},
{48, -18},
{1071, 462},
{17, 19}
};
for (int[] pair : testPairs) {
int a = pair[0];
int b = pair[1];
int gcdRec = gcdRecursive(a, b);
int gcdIter = gcdIterative(a, b);
int gcdBin = binaryGcd(a, b);
System.out.printf("gcd(%3d, %3d) = %2d (递归) = %2d (迭代) = %2d (二进制)%n",
a, b, gcdRec, gcdIter, gcdBin);
}
// 2. 扩展欧几里得算法
System.out.println("\n2. 扩展欧几里得算法:");
int[][] extendedPairs = {
{48, 18},
{56, 98},
{17, 19}
};
for (int[] pair : extendedPairs) {
int a = pair[0];
int b = pair[1];
int[] result = extendedGcd(a, b);
int gcd = result[0];
int x = result[1];
int y = result[2];
System.out.printf("%d*%d + %d*%d = %d", a, x, b, y, gcd);
System.out.printf(" 验证: %d*%d + %d*%d = %d %s%n",
a, x, b, y, a*x + b*y,
(a*x + b*y == gcd) ? "✓" : "✗");
}
// 3. 最小公倍数计算
System.out.println("\n3. 最小公倍数计算:");
for (int[] pair : testPairs) {
int a = pair[0];
int b = pair[1];
int lcm = lcm(a, b);
int gcd = gcdSafe(a, b);
if (a != 0 && b != 0) {
System.out.printf("lcm(%3d, %3d) = %6d, 验证: %d*%d/gcd = %d%n",
a, b, lcm, a, b, Math.abs(a*b)/gcd);
}
}
// 4. 多个数的GCD和LCM
System.out.println("\n4. 多个数的GCD和LCM:");
int[] numbers1 = {12, 18, 24};
int[] numbers2 = {15, 25, 35, 45};
int gcdMulti1 = gcdMultiple(numbers1);
int lcmMulti1 = lcmMultiple(numbers1);
System.out.printf("GCD(%s) = %d, LCM(%s) = %d%n",
Arrays.toString(numbers1), gcdMulti1,
Arrays.toString(numbers1), lcmMulti1);
int gcdMulti2 = gcdMultiple(numbers2);
int lcmMulti2 = lcmMultiple(numbers2);
System.out.printf("GCD(%s) = %d, LCM(%s) = %d%n",
Arrays.toString(numbers2), gcdMulti2,
Arrays.toString(numbers2), lcmMulti2);
// 5. 大数计算
System.out.println("\n5. 大数计算:");
BigInteger bigA = new BigInteger("12345678901234567890");
BigInteger bigB = new BigInteger("98765432109876543210");
BigInteger bigGcd = gcdBigInteger(bigA, bigB);
System.out.printf("GCD(%s,%n %s) =%n %s%n",
bigA, bigB, bigGcd);
// 6. 性能比较
System.out.println("\n6. 算法性能比较:");
compareAlgorithmPerformance();
}
private static void compareAlgorithmPerformance() {
int a = 123456789;
int b = 987654321;
int iterations = 1000000;
// 递归算法
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
gcdRecursive(a, b);
}
long recursiveTime = System.nanoTime() - start;
// 迭代算法
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
gcdIterative(a, b);
}
long iterativeTime = System.nanoTime() - start;
// 二进制算法
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
binaryGcd(a, b);
}
long binaryTime = System.nanoTime() - start;
System.out.printf("递归算法: %.3f ms%n", recursiveTime / 1_000_000.0);
System.out.printf("迭代算法: %.3f ms%n", iterativeTime / 1_000_000.0);
System.out.printf("二进制算法: %.3f ms%n", binaryTime / 1_000_000.0);
}
}
5.3、Stein算法
Stein算法(也称为二进制GCD算法):通过位移运算高效计算最大公约数。
算法特点:
- 不使用除法,只使用位移、加减和与运算
- 特别适合大整数和硬件实现
- 时间复杂度:O(log(min(a, b)))
java
import java.math.BigInteger;
import java.util.*;
/**
* Stein算法(二进制GCD算法)
*/
public class SteinAlgorithm {
/**
* 方法1:基础Stein算法实现
* 时间复杂度:O(log(min(a, b)))
*/
public static int steinGCD(int a, int b) {
if (a == 0) return Math.abs(b);
if (b == 0) return Math.abs(a);
// 处理负数
a = Math.abs(a);
b = Math.abs(b);
// 提取公因子2
int shift = 0;
while (((a | b) & 1) == 0) { // a和b都是偶数
a >>= 1;
b >>= 1;
shift++;
}
// 现在a和b至少有一个是奇数
while ((a & 1) == 0) { // a是偶数
a >>= 1;
}
// 使用Stein算法
do {
while ((b & 1) == 0) { // b是偶数
b >>= 1;
}
// 现在a和b都是奇数
if (a > b) {
int temp = a;
a = b;
b = temp;
}
b = b - a;
} while (b != 0);
return a << shift; // 恢复因子2
}
/**
* 方法2:优化版Stein算法
*/
public static int steinGCDOptimized(int a, int b) {
if (a == 0) return Math.abs(b);
if (b == 0) return Math.abs(a);
a = Math.abs(a);
b = Math.abs(b);
// 使用Java内置函数计算尾随0的个数
int shift = Integer.numberOfTrailingZeros(a | b);
a >>= Integer.numberOfTrailingZeros(a);
do {
b >>= Integer.numberOfTrailingZeros(b);
if (a > b) {
int temp = a;
a = b;
b = temp;
}
b -= a;
} while (b != 0);
return a << shift;
}
/**
* 方法3:迭代实现Stein算法
*/
public static int steinIterative(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
if (a == 0) return b;
if (b == 0) return a;
int shift = 0;
// 移除公因子2
while (((a | b) & 1) == 0) {
shift++;
a >>= 1;
b >>= 1;
}
// 保证a是奇数
while ((a & 1) == 0) {
a >>= 1;
}
// 主循环
while (b != 0) {
// 保证b是奇数
while ((b & 1) == 0) {
b >>= 1;
}
// 使用减法
if (a > b) {
int temp = a;
a = b;
b = temp;
}
b -= a;
}
return a << shift;
}
/**
* 方法4:使用尾递归实现
*/
public static int steinRecursive(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
if (a == 0) return b;
if (b == 0) return a;
if (a == b) return a;
// 如果a和b都是偶数
if ((a & 1) == 0 && (b & 1) == 0) {
return steinRecursive(a >> 1, b >> 1) << 1;
}
// 如果a是偶数,b是奇数
if ((a & 1) == 0) {
return steinRecursive(a >> 1, b);
}
// 如果a是奇数,b是偶数
if ((b & 1) == 0) {
return steinRecursive(a, b >> 1);
}
// 如果a和b都是奇数
if (a > b) {
return steinRecursive((a - b) >> 1, b);
} else {
return steinRecursive(a, (b - a) >> 1);
}
}
/**
* 方法5:大数Stein算法
*/
public static BigInteger steinBigInteger(BigInteger a, BigInteger b) {
a = a.abs();
b = b.abs();
if (a.equals(BigInteger.ZERO)) return b;
if (b.equals(BigInteger.ZERO)) return a;
int shift = 0;
// 提取公因子2
while (a.and(BigInteger.ONE).equals(BigInteger.ZERO) &&
b.and(BigInteger.ONE).equals(BigInteger.ZERO)) {
a = a.shiftRight(1);
b = b.shiftRight(1);
shift++;
}
// 主循环
while (!b.equals(BigInteger.ZERO)) {
// 移除b的因子2
while (b.and(BigInteger.ONE).equals(BigInteger.ZERO)) {
b = b.shiftRight(1);
}
// 保证a <= b
if (a.compareTo(b) > 0) {
BigInteger temp = a;
a = b;
b = temp;
}
b = b.subtract(a);
}
return a.shiftLeft(shift);
}
/**
* 方法6:比较Stein算法和欧几里得算法
*/
public static void compareAlgorithms(int a, int b) {
long start, end;
// Stein算法
start = System.nanoTime();
int steinResult = steinGCDOptimized(a, b);
end = System.nanoTime();
long steinTime = end - start;
// 欧几里得算法
start = System.nanoTime();
int euclidResult = euclideanGCD(a, b);
end = System.nanoTime();
long euclidTime = end - start;
// 二进制算法
start = System.nanoTime();
int binaryResult = binaryGCD(a, b);
end = System.nanoTime();
long binaryTime = end - start;
System.out.printf("gcd(%d, %d) = %d%n", a, b, steinResult);
System.out.printf("Stein算法: %.3f µs%n", steinTime / 1000.0);
System.out.printf("欧几里得算法: %.3f µs%n", euclidTime / 1000.0);
System.out.printf("二进制算法: %.3f µs%n", binaryTime / 1000.0);
// 验证结果
boolean correct = (steinResult == euclidResult && euclidResult == binaryResult);
System.out.println("结果验证: " + (correct ? "✓" : "✗"));
}
/**
* 欧几里得算法
*/
private static int euclideanGCD(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
/**
* 二进制GCD算法
*/
private static int binaryGCD(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
if (a == 0) return b;
if (b == 0) return a;
int shift = 0;
while (((a | b) & 1) == 0) {
a >>= 1;
b >>= 1;
shift++;
}
while ((a & 1) == 0) {
a >>= 1;
}
do {
while ((b & 1) == 0) {
b >>= 1;
}
if (a > b) {
int temp = a;
a = b;
b = temp;
}
b -= a;
} while (b != 0);
return a << shift;
}
public static void main(String[] args) {
System.out.println("========== Stein算法(二进制GCD) ==========\n");
// 1. 测试基本功能
System.out.println("1. 基本功能测试:");
int[][] testPairs = {
{48, 18},
{56, 98},
{1071, 462},
{0, 5},
{5, 0},
{0, 0},
{-48, 18},
{48, -18},
{1, 1},
{17, 19}
};
for (int[] pair : testPairs) {
int a = pair[0];
int b = pair[1];
int stein = steinGCD(a, b);
int steinOpt = steinGCDOptimized(a, b);
int steinIter = steinIterative(a, b);
int steinRec = steinRecursive(a, b);
int euclid = euclideanGCD(a, b);
System.out.printf("gcd(%3d, %3d) = %2d (Stein) = %2d (优化) = %2d (迭代) = %2d (递归) = %2d (欧几里得)%n",
a, b, stein, steinOpt, steinIter, steinRec, euclid);
// 验证一致性
boolean consistent = (stein == steinOpt && steinOpt == steinIter &&
steinIter == steinRec && steinRec == euclid);
if (!consistent) {
System.out.println(" 警告:结果不一致!");
}
}
// 2. 算法性能比较
System.out.println("\n2. 算法性能比较:");
int[][] performanceTests = {
{123456789, 987654321},
{999999937, 999999938}, // 相邻大数
{1 << 20, 1 << 19}, // 2的幂
{2147483647, 2147483646} // 接近int最大值
};
for (int[] pair : performanceTests) {
System.out.printf("\n测试: gcd(%,d, %,d)%n", pair[0], pair[1]);
compareAlgorithms(pair[0], pair[1]);
}
// 3. 大数测试
System.out.println("\n3. 大数测试:");
BigInteger bigA = new BigInteger("12345678901234567890");
BigInteger bigB = new BigInteger("98765432109876543210");
BigInteger steinBig = steinBigInteger(bigA, bigB);
BigInteger euclidBig = bigA.gcd(bigB);
System.out.printf("Stein算法: gcd(%s,%n %s) =%n %s%n",
bigA, bigB, steinBig);
System.out.printf("Java内置: gcd(%s,%n %s) =%n %s%n",
bigA, bigB, euclidBig);
System.out.println("结果一致: " + steinBig.equals(euclidBig) ? "✓" : "✗");
// 4. 批量测试
System.out.println("\n4. 批量测试 (1000个随机数对):");
batchTest(1000);
// 5. 特殊情况测试
System.out.println("\n5. 特殊情况测试:");
specialCasesTest();
}
private static void batchTest(int count) {
Random random = new Random();
int correct = 0;
long totalSteinTime = 0;
long totalEuclidTime = 0;
for (int i = 0; i < count; i++) {
int a = random.nextInt(1000000) + 1;
int b = random.nextInt(1000000) + 1;
long start = System.nanoTime();
int steinResult = steinGCDOptimized(a, b);
long steinTime = System.nanoTime() - start;
start = System.nanoTime();
int euclidResult = euclideanGCD(a, b);
long euclidTime = System.nanoTime() - start;
if (steinResult == euclidResult) {
correct++;
}
totalSteinTime += steinTime;
totalEuclidTime += euclidTime;
}
System.out.printf("正确率: %.1f%%%n", (correct * 100.0 / count));
System.out.printf("平均时间 - Stein: %.3f µs, 欧几里得: %.3f µs%n",
totalSteinTime / (count * 1000.0),
totalEuclidTime / (count * 1000.0));
System.out.printf("性能比: %.2fx%n",
(double) totalEuclidTime / totalSteinTime);
}
private static void specialCasesTest() {
System.out.println("测试边界情况:");
// 测试2的幂
System.out.println("\n1. 2的幂:");
for (int i = 1; i <= 10; i++) {
int a = 1 << i; // 2^i
int b = 1 << (i + 1); // 2^(i+1)
int result = steinGCDOptimized(a, b);
System.out.printf("gcd(2^%d, 2^%d) = %d%n", i, i+1, result);
}
// 测试费波那契数
System.out.println("\n2. 费波那契数:");
int fib1 = 1, fib2 = 1;
for (int i = 3; i <= 10; i++) {
int fib3 = fib1 + fib2;
int result = steinGCDOptimized(fib2, fib3);
System.out.printf("gcd(F%d, F%d) = %d%n", i-1, i, result);
fib1 = fib2;
fib2 = fib3;
}
// 测试质数
System.out.println("\n3. 质数:");
int[] primes = {2, 3, 5, 7, 11, 13, 17, 19};
for (int i = 0; i < primes.length - 1; i++) {
for (int j = i + 1; j < primes.length; j++) {
int result = steinGCDOptimized(primes[i], primes[j]);
System.out.printf("gcd(%d, %d) = %d%n", primes[i], primes[j], result);
}
}
}
}
6、最小公倍数
6.1、什么是最小公倍数
最小公倍数(Least Common Multiple):两个或多个整数公有的倍数中最小的一个。
数学性质:
- 公式:lcm(a, b) = |a × b| / gcd(a, b)
- 对于多个数:lcm(a, b, c) = lcm(lcm(a, b), c)
- 如果a和b互质,则lcm(a, b) = a × b
- lcm(0, n) = 0(定义)
6.2、计算最小公倍数的算法
java
import java.math.BigInteger;
import java.util.*;
/**
* 最小公倍数(LCM)计算器
*/
public class LCMCalculator {
/**
* 方法1:通过GCD计算LCM
* 公式:lcm(a, b) = |a * b| / gcd(a, b)
*/
public static int lcmByGCD(int a, int b) {
if (a == 0 || b == 0) {
return 0; // 定义lcm(0, n) = 0
}
// 先除后乘避免溢出
int gcd = gcd(a, b);
return Math.abs(a / gcd * b);
}
/**
* 计算最大公约数(欧几里得算法)
*/
private static int gcd(int a, int b) {
a = Math.abs(a);
b = Math.abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
/**
* 方法2:质因数分解法
*/
public static int lcmByFactorization(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
a = Math.abs(a);
b = Math.abs(b);
// 质因数分解
Map<Integer, Integer> factorsA = primeFactorization(a);
Map<Integer, Integer> factorsB = primeFactorization(b);
// 合并因数,取最高次幂
Set<Integer> allPrimes = new HashSet<>(factorsA.keySet());
allPrimes.addAll(factorsB.keySet());
int lcm = 1;
for (int prime : allPrimes) {
int expA = factorsA.getOrDefault(prime, 0);
int expB = factorsB.getOrDefault(prime, 0);
int maxExp = Math.max(expA, expB);
lcm *= (int) Math.pow(prime, maxExp);
}
return lcm;
}
/**
* 质因数分解
*/
private static Map<Integer, Integer> primeFactorization(int n) {
Map<Integer, Integer> factors = new HashMap<>();
// 处理因子2
int count = 0;
while (n % 2 == 0) {
n /= 2;
count++;
}
if (count > 0) {
factors.put(2, count);
}
// 处理奇数因子
for (int i = 3; i * i <= n; i += 2) {
count = 0;
while (n % i == 0) {
n /= i;
count++;
}
if (count > 0) {
factors.put(i, count);
}
}
// 处理剩余的质因数
if (n > 1) {
factors.put(n, 1);
}
return factors;
}
/**
* 方法3:逐倍查找法
*/
public static int lcmByBruteForce(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
a = Math.abs(a);
b = Math.abs(b);
int max = Math.max(a, b);
int min = Math.min(a, b);
int lcm = max;
while (lcm % min != 0) {
lcm += max;
// 防止溢出
if (lcm < 0) {
throw new ArithmeticException("溢出: LCM太大");
}
}
return lcm;
}
/**
* 方法4:计算多个数的LCM
*/
public static int lcmMultiple(int... numbers) {
if (numbers == null || numbers.length == 0) {
return 0;
}
int result = numbers[0];
for (int i = 1; i < numbers.length; i++) {
result = lcmByGCD(result, numbers[i]);
}
return result;
}
/**
* 方法5:大数LCM计算
*/
public static BigInteger lcmBigInteger(BigInteger a, BigInteger b) {
if (a.signum() == 0 || b.signum() == 0) {
return BigInteger.ZERO;
}
BigInteger gcd = a.gcd(b);
return a.divide(gcd).multiply(b).abs();
}
/**
* 方法6:优化的大数计算(避免中间溢出)
*/
public static BigInteger lcmBigIntegerSafe(BigInteger a, BigInteger b) {
if (a.signum() == 0 || b.signum() == 0) {
return BigInteger.ZERO;
}
BigInteger gcd = a.gcd(b);
return a.divide(gcd).multiply(b).abs();
}
/**
* 方法7:计算分数的最小公分母
*/
public static int commonDenominator(int[] numerators, int[] denominators) {
if (numerators.length != denominators.length) {
throw new IllegalArgumentException("分子分母数组长度不一致");
}
int lcmDenominators = 1;
for (int denominator : denominators) {
if (denominator == 0) {
throw new ArithmeticException("分母不能为0");
}
lcmDenominators = lcmByGCD(lcmDenominators, Math.abs(denominator));
}
return lcmDenominators;
}
/**
* 方法8:验证LCM性质
*/
public static boolean verifyLCM(int a, int b, int lcm) {
if (a == 0 || b == 0) {
return lcm == 0;
}
// 性质1: lcm能被a和b整除
if (lcm % Math.abs(a) != 0 || lcm % Math.abs(b) != 0) {
return false;
}
// 性质2: 任何a和b的公倍数都能被lcm整除
int testMultiple = Math.abs(a) * Math.abs(b);
return testMultiple % lcm == 0;
}
public static void main(String[] args) {
System.out.println("========== 最小公倍数(LCM)计算器 ==========\n");
// 1. 测试基本LCM计算
System.out.println("1. 基本LCM计算:");
int[][] testPairs = {
{12, 18},
{15, 20},
{7, 13},
{0, 5},
{5, 0},
{8, 12},
{21, 28},
{100, 120},
{1, 1},
{1, 100}
};
for (int[] pair : testPairs) {
int a = pair[0];
int b = pair[1];
int lcmGCD = lcmByGCD(a, b);
int lcmFactor = lcmByFactorization(a, b);
int lcmBrute = lcmByBruteForce(a, b);
System.out.printf("lcm(%3d, %3d) = %5d (GCD法) = %5d (分解法) = %5d (逐倍法)",
a, b, lcmGCD, lcmFactor, lcmBrute);
boolean correct = (lcmGCD == lcmFactor && lcmFactor == lcmBrute);
System.out.println(correct ? " ✓" : " ✗");
}
// 2. 多个数的LCM
System.out.println("\n2. 多个数的LCM:");
int[][] numberSets = {
{12, 18, 24},
{4, 6, 8, 12},
{3, 7, 11},
{1, 2, 3, 4, 5}
};
for (int[] numbers : numberSets) {
int lcm = lcmMultiple(numbers);
System.out.printf("lcm(%s) = %d%n",
Arrays.toString(numbers), lcm);
// 验证
for (int num : numbers) {
if (num != 0 && lcm % Math.abs(num) != 0) {
System.out.println(" 验证失败: " + num + " 不能整除 " + lcm);
}
}
}
// 3. 大数计算
System.out.println("\n3. 大数LCM计算:");
BigInteger bigA = new BigInteger("12345678901234567890");
BigInteger bigB = new BigInteger("98765432109876543210");
BigInteger bigLcm = lcmBigInteger(bigA, bigB);
System.out.printf("lcm(%s,%n %s) =%n %s%n",
bigA, bigB, bigLcm);
// 验证
BigInteger remainderA = bigLcm.remainder(bigA);
BigInteger remainderB = bigLcm.remainder(bigB);
System.out.printf("验证: %s %% %s = %s, %s %% %s = %s%n",
bigLcm, bigA, remainderA, bigLcm, bigB, remainderB);
System.out.println("验证结果: " +
(remainderA.equals(BigInteger.ZERO) && remainderB.equals(BigInteger.ZERO) ? "✓" : "✗"));
// 4. 分数运算
System.out.println("\n4. 分数通分:");
int[] numerators = {1, 2, 3};
int[] denominators = {2, 3, 4};
int commonDenom = commonDenominator(numerators, denominators);
System.out.printf("分数: 1/2, 2/3, 3/4%n");
System.out.printf("最小公分母: %d%n", commonDenom);
System.out.printf("通分后: %d/%d, %d/%d, %d/%d%n",
numerators[0] * (commonDenom / denominators[0]), commonDenom,
numerators[1] * (commonDenom / denominators[1]), commonDenom,
numerators[2] * (commonDenom / denominators[2]), commonDenom);
// 5. 性能比较
System.out.println("\n5. 算法性能比较:");
compareAlgorithmPerformance();
}
private static void compareAlgorithmPerformance() {
int a = 12345;
int b = 67890;
int iterations = 100000;
System.out.printf("测试: lcm(%,d, %,d), 迭代次数: %,d%n", a, b, iterations);
// GCD法
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
lcmByGCD(a, b);
}
long timeGCD = System.nanoTime() - start;
// 分解法
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
lcmByFactorization(a, b);
}
long timeFactor = System.nanoTime() - start;
// 逐倍法
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
lcmByBruteForce(a, b);
}
long timeBrute = System.nanoTime() - start;
System.out.printf("GCD法: %.3f ms%n", timeGCD / 1_000_000.0);
System.out.printf("分解法: %.3f ms%n", timeFactor / 1_000_000.0);
System.out.printf("逐倍法: %.3f ms%n", timeBrute / 1_000_000.0);
// 验证结果一致性
int resultGCD = lcmByGCD(a, b);
int resultFactor = lcmByFactorization(a, b);
int resultBrute = lcmByBruteForce(a, b);
boolean consistent = (resultGCD == resultFactor && resultFactor == resultBrute);
System.out.println("结果一致性: " + (consistent ? "✓" : "✗"));
}
}
7、素数
7.1、什么是素数
素数又称为质数,指在一个大于1的自然数中,除了1和此整数自身外,无法被其他自然数整除的数。比1大,但不是素数的称为合数。0和1既不是素数也不是合数。
素数的分布没有很明显的规律,仍然有很多伟大的数学家对其进行研究。例如,最早的欧几里得、17世纪法国的费尔马、法国的梅森等。
谈到素数,有著名的算术基本定理:任何一个大于1的正整数,可以唯一表示成有限个
素数的乘积。
围绕着素数,数学家提出了各种猜想,内容如下。
- 黎曼猜想:黎曼研究发现,素数分布的绝大部分猜想都取决于黎曼zeta函数的零点位置。黎曼在此基础上,猜想那些非平凡零点都落在复平面中实部为1/2的直线上。
- 孪生素数猜想:对于素数n,如果n+2同样为素数,则称n和n+2为孪生素数。到底有没有无穷多个孪生素数呢?这是一个至今仍无法解决的问题。
- 哥德巴赫猜想:哥德巴赫通过大量的数据猜测,所有不小于6的偶数,都可以表示为两个奇素数之和。后人将其称为"1+1"。并且,对于每个不小于9的奇数,都可以表示为三个奇素数之和。哥德巴赫猜想是数学中的璀璨明珠,至今仍无实质性进展。
近年来,素数在密码学上又发现了新的应用。基于大的素数质因数分解的复杂性,从而构造出广泛应用的公钥密码。这是现代密码学的基础,是密码学领域激动人心的进步。
7.2、计算素数的算法
java
/**
* 欧拉筛法(线性筛法)
*/
public static List<Integer> eulerSieve(int limit) {
if (limit < 2) return new ArrayList<>();
List<Integer> primes = new ArrayList<>();
boolean[] isPrime = new boolean[limit + 1];
Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i <= limit; i++) {
if (isPrime[i]) {
primes.add(i);
}
for (int j = 0; j < primes.size() && i * primes.get(j) <= limit; j++) {
isPrime[i * primes.get(j)] = false;
if (i % primes.get(j) == 0) {
break;
}
}
}
return primes;
}