写在开头:最近开始准备蓝桥杯了,在刷蓝桥杯每周一题,希望自己能在首次参加蓝桥杯能取得自己满意的成绩,加油,好好学习能挣钱。
题目:
[问题描述]
考虑如下的序列生成算法:从整数 n 开始,如果 n 是偶数,把它除以 2;如果 n 是奇数,把它乘 3 加1。用新得到的值重复上述步骤,直到 n = 1 时停止。例如,n = 22 时该算法生成的序列是:22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1
人们猜想(没有得到证明)对于任意整数 n,该算法总能终止于 n = 1。这个猜想对于至少 1 000 000内的整数都是正确的。
对于给定的 n,该序列的元素(包括 1)个数被称为 n 的循环节长度。在上述例子中,22 的循环节长度为 16。输入两个数 i 和 j,你的任务是计算 i 到 j(包含 i 和 j)之间的整数中,循环节长度的最大值。
[输入]
输入每行包含两个整数 i 和 j。所有整数大于 0,小于 1 000 000。
[输出]
对于每对整数 i 和 j,按原来的顺序输出 i 和 j,然后输出二者之间的整数中的最大循环节长度。这三个整数应该用单个空格隔开,且在同一行输出。对于读入的每一组数据,在输出中应位于单独的一行。
[样例输入]
1 10
100 200
201 210
900 1000
[样例输出]
1 10 20
100 200 125
201 210 89
900 1000 174
想法:
初次读这道题的时候,有点觉得摸不到头脑,再读仔细一想还是有那么一些想法的,所以得多读几遍题,一是提取出题中所有的有用信息,便于解题,二是有些细节可能不影响某个答案,但是在判题的时候可能导致错误。经过初步的分析后,首先我想到不是怎么解这个题,而是想到了在计算的过程中,很有可能会重复造轮子,当然这取决于输入的数,输入的数越大,两个数之间的范围越大,重复造的轮子就越多,很大程度上会影响时间复杂度,该种解法写在解法2,一般解法即解法1就比较简单了,写一个函数用来算循环节长度,在主函数for循环中反复调用这个函数直到计算完给出的两个数之间的所有数,这其中在用一个容器将每个数的循环节长度,存起来,最后把最大的循环节长度提取出来。解法2就是在解法1的基础上,每计算一个数的时候,先在容器里进行检索,看看是否已经计算过了,如果已经计算了就直接调用该数的循环节长度,没计算就计算后存在容器里,当然检索的过程,也有很多门道了,要想继续优化,可以在检索上继续研究。
解法1:
import java .util.Scanner;
import java.util.Arrays;
public class week1_01 {
static int solue(long f) { //为什么用long而不是int,因为在运算的过程中一个数有可能连续做好几个3n+1,如果这个数比较大的话,就有可能超过int的范围
int count=1; //循环节长度
while (f != 1) {
if (f % 2 == 0) {
f = f / 2;
count++;
} else {
f = 3 * f + 1;
count++;
}
}
return count;
}
public static void main(String args[]){
Scanner sc =new Scanner(System.in);
int m=sc.nextInt();
int n=sc.nextInt();
int temp1,temp2;
if(m>n){ //有可能不是小数在前,先判断,保证循环的时候小的数在前面
temp1=m;
temp2=n;
}else{
temp1=n;
temp2=m;
}
int temp3=temp1-temp2+1;//存循环节长度的数组的容量,比如0到10之间有11个数
int i,j;
int[]num=new int[temp3];
for(j=temp2,i=0;j<=temp1;j++,i++){ //假如0到10,i等于10的时候,循环该结束,下一次循环无法进行,但是i还是会加1;
num[i]=solue(j);
}
Arrays.sort(num,0,i); //排序,下标0到i,但并不包括下标为i的那个数
System.out.println(temp2+" "+temp1+" "+num[i-1]); //循环节长度最大的数
// System.out.println(solue(159487)); 检验一下大的数是否能计算出
}
}
运行结果
解法2:
最开始我想到的是,用数组作为容器,但是数组做容器有很多不方便的地方,比如我检索的是时候,无论用什么方法检索,我都希望检索到的数通过某种方式对应着该数的循环节长度,如果用数组的话,如果将下标设置为检索的数,而该下标存储的数为循环节长度,那么要检索一个很大很大的数,那么数组的长度将十分大,浪费空间,并且定义数组的时候,初始化长度也不能确认。当我看到官方给出的题解的时候,让我醍醐灌顶,采用hashmap这个容器简直就是,完美匹配
代码如下:
//不愧是官方题解,每一步都很巧妙
import java.util.HashMap;
import java.util.Scanner;
public class week1_02 {
static HashMap<Integer, Integer> map = new HashMap<>(); //泛型
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int i = sc.nextInt();
int j = sc.nextInt();
int a = i;
int b = j;
if (i > j) {
a = j;
b = i;
}
int max = -1;
for (int k = a; k <= b; k++) {
Integer fk = map.get(k);//检索步骤,假如k等于900,假如还没有计算过900这个数,hashmap中就不存在这个数,fk=null,假如已经存在,就直接取到了900的循环节长度
if (fk == null) { //如果不存在fk这个数,
fk = f(k); //计算该数的循环节长度
map.put(k, fk);//存入hashmap中
}
max = Integer.max(max, fk);//把前后两次大的循环节长度存为max,遇到更大的不断替换
}
System.out.println(i + " " + j + " " + max);
}
}
static int f(long k) {
int count = 1;
while (k != 1) {
if ((k & 1) == 0) {
k /= 2;
} else {
k = k * 3 + 1;
}
count++;
}
return count;
}
}
运行结果
结束语:
路还很长,坚持!