1.逃离高塔
问题描述:
小蓝一觉醒来,小蓝被困在一座高耸的塔中。这座塔共有 20252025 层,每一层都刻有一个数字的立方值,从底层的 1313、2323、3333、⋯⋯,一直到顶层的 2025320253,层层叠叠,直入云霄。塔顶有一扇门,门旁刻着一行字:"若想离开此塔,需找出这些立方数中个位数字为 33 的数的个数。"
小蓝非常着急,因为他需要尽快离开这座塔,去参加即将到来的蓝桥杯比赛。时间紧迫,请你帮助他解答这个问题。
参考代码:
java
package practice4;
public class Test4 {
public static void main(String[] args) {
//1.定义变量统计个数
int sum=0;
//2.循环判断符合要求的数
for (int i = 1; i <= 2025; i++) {
//3.求出立方数
/*注意:2025的立方非常大,要用long存储*/
long result =(long) Math.pow(i, 3);
//4.判断个位数是否为3
if(result%10==3) sum++;
}
//5.输出
System.out.println(sum);
}
}
当然了,经验丰富的小伙伴会知道立方后个位数为3的数个位只能为7,根据这个,不需要代码就能推出来。
2.消失的蓝宝:
问题描述:
"蓝宝又不见了!" 2025 年 4 月 12 日,蓝桥杯吉祥物"蓝宝"在省赛前夕突然失踪。小蓝翻阅了蓝宝的活动记录,发现它的出现时间似乎与蓝桥杯的两个重要日期密切相关:
- 第十六届省赛日期 2025041220250412,
- 第十五届省赛日期 2024041320240413。
经过分析,小蓝推测蓝宝的下一次出现时间 NN 满足以下条件:
- N+20250412N+20250412 能被 2024041320240413 整除。
- N+20240413N+20240413 能被 2025041220250412 整除。
现在,请你帮小蓝找出满足条件的最小正整数 NN,预测蓝宝的下一次出现时间。
参考代码:
java
package practice6;
public class Test {
public static void main(String[] args) {
//1.记录变量
int num1 = 20250412;
int num2 = 20240413;
//2.暴力枚举
for (long i = 2 ;i<=10000000000000L;i++){ //i从1开始n会有负数
//3.先根据第一个条件确定n,再判断n是否符合第二个条件
long n = num2*i - num1;
//4.判断
if ((n+num2)%num1==0){
System.out.println(n);
break;
}
}
}
}
另一种思路:
设n+20250412=20240413a,n+20240413=20250412b,则20240413*(a+1)=20250412*(b+1),又因为20240413和20250412互质,所以a+1最小为20250412,则n=20240413*a-20250412=20250411 * 20240413 - 20250412。
3.电池分组:
问题描述:
研究员小蓝受到实验室主任的指示,需要对实验室新研发的 n 个新型能量电池进行分组实验。这 N 个能量电池的能量值分别用 A1,A2,...,AN表示,每个能量值都是一个整数。为了保证实验的安全性,小蓝需要将这 N 个能量电池分成两组,使得这两组能量电池的能量值异或和相等。
能量值的异或和计算方法如下:对于一个集合 S,其异或和等于集合中所有元素的按位异或结果。例如,集合 {1,2,3} 的异或和为 1⊕2⊕3=0,其中 ⊕ 表示异或运算。
现在,小蓝想知道,这 n个能量电池能否分成两组,使得这两组能量电池的能量值异或和相等。注意,每组至少包含一个能量电池。
请你帮帮他!
输入格式:
输入的第一行包含一个整数 T ,表示测试用例的数量。
每个测试用例占两行:
- 第一行包含一个整数 N,表示能量电池的数量。
- 第二行包含 n 个整数 A1,A2,...,AN,表示每个能量电池的能量值。
输出格式:
对于每个测试用例,输出一行。如果可以将能量电池分成两组,使得这两组能量电池的能量值异或和相等,则输出 YES;否则,输出 NO。
样例输入:
2
3
1 2 3
4
1 2 3 4
样例输出:
YES
NO
解题思路:
先假设条件成立,能分为两个异或和相等的组,那么此时两组的异或和为0。也就意味着只要所有数据的异或和为0,就可以将其划分为异或和相等的两组数据.
其中不是两组的异或和各自"必须为 0",而是当两组异或和相等时,总异或和一定为 0,所以要把集合中的所有元素都异或起来,看是否为0;反过来,当总异或和为 0 时,两组的异或和必然相等(可以是任意值,比如 1、3、5 等等)。
参考代码:
java
package practice4;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
//1.输入集合个数
int num= scan.nextInt();
//2.判断能否分组
/*根据题意,只有集合的所有数据经过异或运算后结果为0,才能分成两组,
* 只要有一个为1,就说明存在不符合题意的*/
while (num>0){
//定义变量记录异或结果
long result=0;
//1.输入集合元素个数
int len = scan.nextInt();
//2.循环输入集合元素的数据并进行异或运算
for (int i = 0; i < len; i++) {
int data= scan.nextInt();
result = result ^ data;
}
//3.判断
if(result==0) System.out.println("YES");
else System.out.println("NO");
//4.开始下一个集合判断
num--;
}
}
}
4.魔法科考试:
问题描述:
小明正在参加魔法科的期末考试,考生需要根据给定的口诀组合出有效的魔法。其中,老师给定了 n 个上半部分口诀 a1,a2,...,ana1,a2,...,an 和 m 个下半部分口诀 b1,b2,...,bmb1,b2,...,bm,均用整数表示。完整的口诀包含一个上半部分口诀和一个下半部分口诀,当选用两个口诀 ai和 bj,将组合出完整口诀 S=ai+bj。
当 S满足 S≤n+m 且 S 为质数时,魔法是有效的。魔法的种类只和 S 的大小有关。如果每个上半部分口诀和每个下半部分口诀在不同的组合中可以重复使用,小明想知道一共可能组合出多少种不同的有效魔法?
输入格式:
输入共三行。
- 第一行为两个正整数 n,m。
- 第二行为 nn 个由空格分开的正整数 a1,a2,...,an。
- 第三行为 mm 个由空格分开的正整数 b1,b2,...,bm。
输出格式:
输出共 1行,一个整数表示答案。
样例输入:
3 4
2 3 10
3 4 5 1
样例输出:
3
样例说明:
可以组合出 3,5,7 这三个有效魔法。
注意事项:
本体要注意的是质数判断以及有效魔法的不重复性。
参考代码:
不严谨的代码:
java
package practice5;
import java.util.HashSet;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
//1.分别输入两个数组长度
int len1=scan.nextInt();
int len2=scan.nextInt();
//2.分别定义两个数组并录入数据
int[] a=new int[len1];
for (int i = 0; i < len1; i++) {
a[i]=scan.nextInt();
}
int[] b=new int[len2];
for (int i = 0; i < len2; i++) {
b[i]= scan.nextInt();
}
//3.判断
HashSet<Integer> result=new HashSet<>();
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
if( a[i]+b[j]<=len1+len2 &&method(a[i]+b[j])){
result.add(a[i]+b[j]);
}
}
}
//4.输出
System.out.println(result.size());
}
//定义一个函数,判断当前数是否为质数
private static boolean method(int number){
double temp=Math.sqrt(number);
int top = (int) Math.ceil(temp); //开根号后向上取整
for (int i = 2; i <= top; i++) {
if(number%i==0) return false; //不是质数
}
//此时就是质数
return true;
}
}
严谨的代码以及上述代码的漏洞解读:
a、先分析判断质数方法的问题:
质数判断函数 method() 存在两个关键错误,会导致结果不准确:
- 未处理小于 2 的数 :质数的定义是「大于 1 的自然数,只能被 1 和自身整除」,但代码没有判断
number <= 1的情况,会把1、0、负数错误判定为质数。 - 向上取整多余且可能出错 :
Math.ceil(Math.sqrt(number))会导致循环多执行一次(比如number=4,sqrt(4)=2,ceil 后还是 2,没问题;但number=5,sqrt(5)≈2.236,ceil 后是 3,实际只需要循环到 2 即可),正确做法是直接取整后循环,或用i*i <= number避免浮点运算。
b、代码优化方案(提速 + 修正):
优化核心思路:
- 修正质数判断逻辑:补全边界条件,去掉多余的浮点运算,减少循环次数。
- 减少重复计算 :提前计算
len1+len2的值(无需每次循环都计算)。 - 减少无效循环 :在两数相加时,若和超过
len1+len2直接跳过(提前剪枝)。 - 质数判断提速:偶数直接排除(除了 2),循环步长改为 2,减少一半循环次数。
优化后的完整代码:
java
package practice5;
import java.util.HashSet;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 1. 输入数组长度
int len1 = scan.nextInt();
int len2 = scan.nextInt();
// 2. 录入数组数据
int[] a = new int[len1];
for (int i = 0; i < len1; i++) {
a[i] = scan.nextInt();
}
int[] b = new int[len2];
for (int i = 0; i < len2; i++) {
b[i] = scan.nextInt();
}
scan.close(); // 关闭Scanner,释放资源
//边界防护:数组为空的情况
if(len1<=0||len2<=0){
System.out.println(0);
return;
}
// 3. 核心逻辑:提前计算总和阈值,避免重复计算
int sumThreshold = len1 + len2;
HashSet<Integer> result = new HashSet<>();
for (int numA : a) { // 增强for循环,可读性更高
for (int numB : b) {
int sum = numA + numB;
// 提前剪枝:和超过阈值直接跳过,减少质数判断的调用
if (sum > sumThreshold) {
continue;
}
// 调用优化后的质数判断方法
if (isPrime(sum)) {
result.add(sum);
}
}
}
// 4. 输出结果
System.out.println(result.size());
}
/**
* 优化后的质数判断方法
* 1. 处理边界:小于2不是质数,2是唯一偶质数
* 2. 排除偶数:减少一半循环次数
* 3. 用i*i <= number替代sqrt,避免浮点运算误差
*/
private static boolean isPrime(int number) {
// 边界1:小于2的数不是质数
if (number <= 1) {
return false;
}
// 边界2:2是唯一的偶质数
if (number == 2) {
return true;
}
// 边界3:偶数(且大于2)不是质数
if (number % 2 == 0) {
return false;
}
//此时该数必定为奇数,那么如果不是质数,那么因数必定不是偶数
// 核心循环:只判断奇数,从3开始,步长2,到sqrt(number)为止
for (int i = 3; i * i <= number; i += 2) {
if (number % i == 0) {
return false;
}
}
return true;
}
}
c、关键优化点解释:
| 优化点 | 原代码问题 | 优化后效果 |
|---|---|---|
| 质数判断边界处理 | 未处理 number<=1,错误判定非质数 | 结果准确,避免无效循环 |
| 排除偶数 + 步长 2 | 循环遍历所有数,效率低 | 循环次数减少 50%,大幅提速 |
| i*i <= number | 用 sqrt+ceil,浮点运算有误差且耗时 | 整数运算更高效,无精度问题 |
| 提前计算 sumThreshold | 每次循环都计算 len1+len2,重复运算 | 只计算 1 次,减少冗余计算 |
| 提前剪枝 sum > 阈值 | 无效调用质数判断方法 | 减少不必要的质数判断,节省时间 |
| 增强 for 循环 | 普通 for 循环可读性差 | 代码更简洁,无性能损耗 |
| 关闭 Scanner | 未关闭资源,可能导致内存泄漏 | 资源释放更规范,尤其大数据量时更稳定 |
d、额外提速建议(针对大数据量):
如果输入的数组长度很大(比如 len1、len2 都是 10^4 级别),可以再做以下优化:
-
预缓存质数 :提前计算
0~(len1+len2)范围内的所有质数(用埃氏筛 / 欧拉筛),后续直接查表,避免重复判断质数。示例(埃氏筛优化):java// 在main方法中,提前生成质数缓存 int maxNum = len1 + len2; boolean[] isPrimeCache = new boolean[maxNum + 1]; // 初始化埃氏筛 for (int i = 2; i <= maxNum; i++) { isPrimeCache[i] = true; } for (int i = 2; i * i <= maxNum; i++) { if (isPrimeCache[i]) { for (int j = i * i; j <= maxNum; j += i) { isPrimeCache[j] = false; } } } // 后续判断时直接用:if (isPrimeCache[sum]) -
替换 HashSet:如果不需要去重(题目中 sum 可能重复,HashSet 是必要的),但可以用布尔数组替代 HashSet,查询更快。
e.总结:
- 质数判断核心修正 :必须处理
number<=1和偶数,用i*i <= number替代浮点运算,结果才准确且高效。 - 性能优化关键:提前剪枝(sum 超过阈值直接跳过)、排除偶数循环、减少重复计算,能让代码运行速率提升 50% 以上。
- 大数据量进阶:埃氏筛预缓存质数是最优方案,避免多次重复判断同一个数是否为质数。
d.关于判断是否为质数的方法中for循环中不判断偶数的原因:
不是 "不判断偶数",而是 "提前判断偶数并直接排除",这是质数判断里最经典的提速技巧,我把这个逻辑拆透:
- 质数的定义是:大于 1 的自然数,只能被 1 和自身整除
- 除了数字 2 之外,所有偶数都不可能是质数->原因很简单:偶数的定义是 "能被 2 整除的数",除了 2 之外,所有偶数都至少有 3 个因数(1、2、自身),违反质数的定义
- 2是唯一的偶质数
- 提前判断偶数" 的核心答案:减少无效循环,无精度风险