
本文探讨了如何在给定范围内查找阶乘首位为偶数的数字。针对传统数据类型在计算大数阶乘时可能遇到的溢出问题,文章详细介绍了如何利用java的`biginteger`处理任意大小的阶乘结果,并结合备忘录模式(memoization)优化计算过程,避免重复计算,从而实现高效且准确的解决方案。
我们的目标是在一个指定的整数范围 [a, b] 内,找出所有满足其阶乘结果的首位数字为偶数的整数。例如,在 [1, 10] 的范围内,2! = 2 (首位为偶数), 3! = 6 (首位为偶数), 4! = 24 (首位为偶数), 8! = 40320 (首位为偶数)。因此,该范围内的结果应为 [2, 3, 4, 8]。
在处理阶乘计算时,一个常见的陷阱是使用标准整数类型(如 int 或 long)来存储结果。阶乘函数增长非常迅速:
考虑以下原始代码片段:
List<Integer> process(int a, int b) {
long base = i; // 这里的i应该初始化为1,并计算a!
for(int i=1; i<=a; i++) base *= i; // 假设这里是计算a!
if(even(base)) list.add(a); // 这里的even(base)在溢出后会给出错误结果
for(int i=a+1; i<=b; i++) {
base *= i; // 从a+1开始,base继续乘以i
if(even(base)) list.add(i);
}
return list;
}
boolean even(long k) {
// 将long转换为字符串,然后检查首位
int z = ("" + k).charAt(0) - '0';
return z % 2 == 0;
}这段代码在 a 或 b 较大时会失败,因为 long 类型的 base 会在 20! 左右溢出。一旦溢出,base 的值将不再代表真实的阶乘结果,后续通过 ("" + k).charAt(0) 获取的首位数字也将是错误的。这就是为什么在编码挑战中会有隐藏测试用例失败的原因。
为了解决 long 类型溢出的问题,我们需要使用能够处理任意精度整数的类型。在 Java 中,java.math.BigInteger 类正是为此目的设计的。BigInteger 对象可以存储任意大小的整数,从而确保阶乘计算的准确性,无论数字有多大。
使用 BigInteger 进行阶乘计算的基本思路是:
在给定范围 [a, b] 内查找数字时,我们可能需要计算多个连续的阶乘(例如 5!、6!、7! 等)。每次都从头开始计算阶乘效率较低。备忘录模式(或动态规划的自底向上方法)可以显著提高效率。
其思想是:
我们将创建一个 Fact 记录(或一个简单的类)来存储每个数字 n 对应的 BigInteger 阶乘值和其首位数字的奇偶性。
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class FactorialFirstDigitParity {
// Fact 记录用于存储阶乘结果、对应的数字n以及首位数字的奇偶性
// parity为true表示首位为偶数,false表示首位为奇数
record Fact(BigInteger fact, int n, boolean parity) {}
// 静态列表用于存储已计算的阶乘结果,实现备忘录模式
// 初始化时包含0!, 1!, 2! 的结果
static List<Fact> computed = new ArrayList<>(List.of(
new Fact(BigInteger.ONE, 0, false), // 0! = 1 (首位为奇数)
new Fact(BigInteger.ONE, 1, false), // 1! = 1 (首位为奇数)
new Fact(BigInteger.TWO, 2, true) // 2! = 2 (首位为偶数)
));
/**
* 判断给定数字 n 的阶乘首位是否为偶数。
* 使用备忘录模式优化。
* @param n 要计算阶乘的数字
* @return 如果 n! 的首位为偶数,则返回 true;否则返回 false。
*/
public static boolean factorialStartsWithEven(int n) {
// 如果 n 小于等于当前已计算的最大阶乘数,直接从备忘录中获取结果
if (n < computed.size()) {
return computed.get(n).parity;
}
// 获取备忘录中最后一个已计算的阶乘结果
Fact lastComputedFact = computed.get(computed.size() - 1);
BigInteger currentFactValue = lastComputedFact.fact; // 从上一个阶乘值开始
Fact result = null;
// 从上一个已计算的数字 n+1 开始,逐步计算到目标 n
for (int k = lastComputedFact.n + 1; k <= n; k++) {
// 计算新的阶乘值: (k-1)! * k
currentFactValue = currentFactValue.multiply(BigInteger.valueOf(k));
// 获取当前阶乘值的字符串表示,并提取首位数字
char firstChar = currentFactValue.toString().charAt(0);
int firstDigit = Character.digit(firstChar, 10); // 将字符转换为数字
// 判断首位数字的奇偶性
boolean isEvenParity = (firstDigit % 2 == 0);
// 创建新的 Fact 记录并添加到备忘录中
result = new Fact(currentFactValue, k, isEvenParity);
computed.add(result);
}
// 返回目标 n 的阶乘首位奇偶性
return result.parity;
}
/**
* 在指定范围内查找所有阶乘首位为偶数的数字。
* @param start 范围的起始值
* @param end 范围的结束值
* @return 包含所有符合条件的数字的列表
*/
public static List<Integer> getForRange(int start, int end) {
List<Integer> results = new ArrayList<>();
for (int i = start; i <= end; i++) {
if (factorialStartsWithEven(i)) {
results.add(i);
}
}
return results;
}
public static void main(String[] args) {
// 示例用法:查找 1 到 20 之间阶乘首位为偶数的数字
List<Integer> list = getForRange(1, 20);
System.out.println("在 [1, 20] 范围内,阶乘首位为偶数的数字有: " + list);
// 预期输出: [2, 3, 4, 8, 12, 13, 14, 16, 18, 20]
// 示例用法:查找 1 到 10 之间阶乘首位为偶数的数字
List<Integer> listSmallRange = getForRange(1, 10);
System.out.println("在 [1, 10] 范围内,阶乘首位为偶数的数字有: " + listSmallRange);
// 预期输出: [2, 3, 4, 8]
}
}代码说明:
通过上述方法,我们可以健壮且高效地解决在给定范围内查找阶乘首位数字为偶数的问题,即使涉及大数阶乘计算也能保证准确性。
以上就是使用BigInteger和备忘录模式高效判断阶乘首位数字奇偶性的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号