目录
题目
话不多说先上题
题目描述
任意给定一个正整数N (N < 1E6 + 1),
如果是偶数,执行: N / 2
如果是奇数,执行: N * 3 + 1
生成的新的数字再执行同样的动作,循环往复。
通过观察发现,这个数字会一会儿上升到很高,
一会儿又降落下来。
就这样起起落落的,但最终必会落到“1”
这有点像小冰雹粒子在冰雹云中翻滚增长的样子。
比如N=9
9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1
可以看到,N=9的时候,这个“小冰雹”最高冲到了52这个高度。
输出格式
一个正整数,表示不大于N的数字,经过冰雹数变换过程中,最高冲到了多少。
例如,输入:
10
程序应该输出:
52
再例如,输入:
100
程序应该输出:
9232
资源约定
峰值内存消耗:< 256MB
CPU消耗:< 1s
题目解法
暴力解法
很多人第一时间都会想到单纯使用 while / for 循环完成这题,然后用一个变量 max 记录最大值,但是也需要注意一点,“不大于N的数字”,也就是说如果输入 100,则需要计算 1 - 100 中每一个数字的冰雹变换,如果你没有想到这个,你只能得到 12 分
JAVA (52分)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt(); // < 1000000
int max = 0;
for(int i = 1; i <= N; i++) {
int temp = i;
while(temp != 1) {
if(temp % 2 != 0) {
temp = (temp * 3) + 1;
max = Math.max(temp, max);
}else {
temp /= 2;
}
}
}
System.out.println(max);
sc.close();
}
}
Python (50分)
N = int(input())
max_num = 1
for i in range(1, N):
while i != 1:
if (i & 1) == 1:
i = (i * 3) + 1
max_num = max(i, max_num)
else:
i = i // 2
print(max_num)
这个方法是最容易想到且最容易实现的,但是需要消耗的时间却至少是 N * N!,而且别忘了 temp % 2 也是需要消耗很多时间的。除去时间问题以外,max 还是 int 类型,在这题 1E6 + 1的大数字下经过几轮运算也会溢出,这也是很常见的问题
运行结果:
很显然,暴力解法对于 JAVA 这种慢吞吞的语言来说不管用,Python 在这题更只得了 50 分的低分(第五题超时),连及格线都没达到
字典解法
如果写过几次力扣的童鞋就会发现,很多速度很快的代码都和 HashMap 有关,因为他检索速度是 O(1) ,这也是为什么很多算法带佬喜欢他的原因,我们不妨也用这个方法,将每次运算获取的最大值和开始数字 i 绑定,在其他数字运算时碰到数字 i 时便可以直接同步这个最大值。
JAVA(87分)
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long N = sc.nextLong(); long max = 0;
Map<Long, Long> dict = new HashMap<Long, Long>();
for(long i = 1; i <= N; i++) {
long temp = i;
while(temp != 1) {
if(dict.containsKey(temp))
break;
if((temp & 1) == 1) {
temp = (temp * 3) + 1;
max = Math.max(temp, max);
}else {
temp /= 2;
}
if(temp == 1)
dict.put(i, max);
}
}
System.out.println(max);
sc.close();
}
}
让我们浅运行一下吧
什么?内存超限?原来一个 long 类型占用内存是 8 字节,而 map 中 key 和 value 都分配了 long 类型,那么我们把 key 改为 Integer 类型可以吗?很抱歉的告诉你,改为 Integer 这题也需要消耗 300+ MB 内存,而且改成 Integer 不仅不能完全解决内存占用问题,第 8 组测试数据的 CPU 使用超时问题也无法得到完美的解决,因为在内存超限以后程序已经被强行停止运行,CPU使用时间并不能作为准确的参考,这题实际消耗有 1s+
数组解法(内存地址)
map 会超时且超限的原因在于即使时间复杂度只有 O(1) 但是也不够迅速,而且我们细想就能发现 key 值似乎没必要专门储存,int 对 long 这种键值对似乎用其他数据类型就可以满足?没错,我们可以使用数组,而且数组 index 直接指向值储存的内存地址,效率极其高效,无论是判断数据是否存在还是更新数据,数组都快不少。除此之外,我们还能在需要遍历的数字底下下功夫,很明显如果一开始传入的是偶数,那么他最大值一定比他相邻的奇数的最大值小,那么我们就根本没有必要遍历偶数,于是我们就可以把for循环改为 int i = 1;i += 2 或者 int i = 3; i +=2
JAVA(100分)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long N = sc.nextLong(); long max = 0;
long[] arr = new long[1000001];
for(int i = 1; i <= N; i += 2) {
long temp = i;
while(temp != 1) {
if(temp <= 1E6 && arr[(int) temp] != 0)
break;
if((temp & 1) == 1) {
temp = (temp * 3) + 1;
max = Math.max(temp, max);
}else {
temp /= 2;
}
if(temp == 1)
arr[i] = max;
}
}
System.out.println(max);
sc.close();
}
}
浅浅再运行一下吧:
我丢!竟然速度这么快,甚至第八组要比传统 C / C++ 解法还快
But!如果你就止步于此那么你就不够合格,因为你明明知道偶数用不上,那你为什么还要创建这么大的数组???那么完全可以只创建 (1E6 + 2) / 2 大小的数组,在判断是否存在的时候也只需要对开始的奇数加一除二,在内存方面又节省的很大的开销。
而且这题运行时间还有 700ms 多能用,你还可以用时间换空间,在计算到大数的时候,可以让大数与前面小数运算得到的最大值进行比较,然后有选择性的释放小值所占空间,但是没有太大必要这么做,收益与投入不成正比了,除非是极端情况。
还有弱弱的说一句,这题 Python 几乎无解,但是可以使用 numpy 中的数组解决