蓝桥杯 - 冰雹数 角谷猜想 【JAVA 正确解法】

目录

题目

题目描述

输出格式

资源约定

题目解法

        暴力解法

        JAVA (52分)

        Python (50分)

        字典解法

        JAVA(87分)

        数组解法(内存地址)

        JAVA(100分)

题目

话不多说先上题

题目描述

       任意给定一个正整数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 中的数组解决

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值