数据结构与算法--丑数

找出排在第n位大的丑数

  • 丑数:我们将只包含质因子 2,3,5的数称为丑数(ugly Number)。求按从小到大的熟悉怒排列的低1500 个丑数。例如6,8 都是丑数,但是14 不是丑数,因为他包含质因子7。1 是基础丑数

  • 解法一:

    • 最直观的解法,从1 开始逐个判断每个整数是否丑数,
    • 所谓的一个数m是另一个数n的因子,那么n就能被m整除。也就是m%n == 0.
    • 根据丑数定义,丑数只能被2,3,5 整除。也就是说如果一个数能被2 整除,我们可以将它连续除以2。3 和5 也是类似,最后我们得到的是1 ,那么这个数就是丑数。
  • 解法一的实现如下:

public class FindUglyNumber {public static void main(String[] args) {Long time = System.currentTimeMillis();System.out.println(getNumberOfUglyNumber(900));Long time_1 = System.currentTimeMillis();System.out.println(time_1 - time);}/*** 方法一:逐个判断从1 开始的数字是否丑数* */public static Integer getNumberOfUglyNumber(Integer position){if(position <= 0){return -1;}Integer count = 0;for (int i =0; true;i++){if(isUglyNumber(i)){count++;if(count.equals(position)){return i;}}}}/*** 判断是否丑数* */public static boolean isUglyNumber(Integer number) {if(number <= 0){return false;}while (number % 2 == 0) {number /= 2;}while (number % 3 == 0) {number /= 3;}while (number % 5 == 0){number /= 5;}return number == 1;}
}
  • 以上经过测试在求第1500 个丑数的耗时大概需要11秒以上,这个在线上是不可用额,那么我们应该有更好的优化方案

  • 方法二:

    • 既然要逐个判断,如上实现中的isUglyNumber,其中有一个循环计算的过程,那么我们能否减少计算的步骤,直接判断出是否丑数
    • 我们将已找出的丑数放在一个数组中,利用这个数组判断
    • 当找出一个丑数s 时候,那么在这个丑数基础上s * 2,s * 3,s * 5,必然也是丑数
    • 同理,如果有一个数 k, k/2,k/3,k/5 包含在已有的丑数中那么这个数也必然也是丑数
    • 我们利用如上数学原来,改造丑数的判断方法,在每一次计算后判断是否在已有丑数中,就可以减少计算量
    • 如下实现
/*** @author liaojiamin* @Date:Created in 10:57 2021/6/9*/
public class FindUglyNumber {public static void main(String[] args) {Long time_1 = System.currentTimeMillis();System.out.println(getNumberOfUglyNumber_1(900));Long time_2 = System.currentTimeMillis();System.out.println(time_2 - time_1);}/*** 方法二:保存之前判断过的丑数,丑数* (2/3/5) 必然还是丑数* 更慢* */public static Integer getNumberOfUglyNumber_1(Integer position){if(position <=0 ){return -1;}List<Integer> uglyNumberList = new ArrayList<>();Integer count =0;for (int i=0;true;i++){if(isUglyNumber(i, uglyNumberList)){count ++;if(count.equals(position)){return i;}uglyNumberList.add(i);}}}/*** 判断是否丑数* */public static boolean isUglyNumber(Integer number, List<Integer> uglyNumberList) {if(number <= 0){return false;}while (number % 2 == 0) {number /= 2;if(uglyNumberList.contains(number)){return true;}}while (number % 3 == 0) {number /= 3;if(uglyNumberList.contains(number)){return true;}}while (number % 5 == 0){number /= 5;if(uglyNumberList.contains(number)){return true;}}return number == 1;}
}
  • 如上方案运行后结果并没有如我们预期得到优化,时间反而更长了,基本要n分钟才能出结果

  • 问题在循环运算的过程中我们每次运算都会判断结果是否包含在已有丑数中

  • 但是 List的content方法的耗时比实际运算的时间多多了,因此它的耗时反而增加了

  • 本想空间换时间,但是算法应该没写好,是一次失败的优化,接着找更优的方案

  • 方案三:

    • 以上两种方案都是对数字逐个判断,方案二中的思路是可以借鉴的,是否还有更优的解,无需逐个判断我们通过算法求出还存在的丑数
    • 在方案二中我们提出了这个数学规律:当找出一个丑数s 时候,那么在这个丑数基础上s * 2,s * 3,s * 5,必然也是丑数
    • 有如上规则的话,我们就有办法通过这个规则找出还没有界别出的丑数,但是难点在于我们需要按顺序排列
    • 按顺序找的话,我们只能找出比当前找出的最大丑数 大的最小丑数,比较拗口,举例如下
    • 当前丑数有{1,2,3,4} 那么我们能通过2,3,5 的乘法找出2,3,4,6,10 ,但是符合当前要求的只有5
    • 因此我们用如下规则查询:
      • 将2,3,5 与现有丑数数组中所有的数字依次做乘法,并且将大于当前最大丑数max的值记录下来记为k
      • 找出记录k中最小的值,就是我们需要找的下一个丑数
    • 经过如上分析有如下代码:
/*** @author liaojiamin* @Date:Created in 10:57 2021/6/9*/
public class FindUglyNumber {public static void main(String[] args) {Long time_2 = System.currentTimeMillis();System.out.println(getNumberOfUglyNumber_2(1500));System.out.println(System.currentTimeMillis() - time_2);}/*** 方法三:不逐个判断,我们先将1 是基础丑数放入已经查找数组中,此时最大丑数是1 记为max,* 因为 uglyNum * (2/3/5) 还是uglyNum,我们直接找正好大于 当前max的最小丑数,* 此时将1 分别* (2/3/5) 得到 (2/3/5),其中最小值2,此时 max = 2* 以此类推* */public static Integer getNumberOfUglyNumber_2(Integer position){if(position <= 0){return -1;}Integer[] uglyArray = new Integer[position];uglyArray[0] = 1;Integer nowPosition = 1;Integer max = uglyArray[0];while (nowPosition < position){Integer newMax = findMinUgly(max, uglyArray, nowPosition);max = newMax;uglyArray[nowPosition] = newMax;nowPosition++;}return uglyArray[position - 1];}public static Integer findMinUgly(Integer max, Integer[] uglyArray, Integer nowPosition){Integer min = Integer.MAX_VALUE;for (int i = 0; i< nowPosition ; i++) {Integer temp_2 = uglyArray[i] * 2;if(temp_2 > max && temp_2 < min){min = temp_2;}Integer temp_3 = uglyArray[i] * 3;if(temp_3 > max && temp_3 < min){min = temp_3;}Integer temp_5 = uglyArray[i] * 5;if(temp_5 > max && temp_5 < min){min = temp_5;}}return min;}
}
  • 经过如上优化后,第1500 位丑数的求解控制在46毫秒以内这基本上可以算合格的解法了
  • 但是其实还是有优化空间的,也就是我们在findMinUgly中循环做乘法运算求最小丑数的时候,有一部分乘法是完全没必要的
  • 例如当我们当前的最大丑数是k的时候,在2,3,5与第 n个丑数做乘法的结果中 5*n < k
  • 其中5*n是n 以及n之前 所有丑数能计算出的最大丑数还是 小于 k,就说明n 之前的所有乘法都是不必要的
  • 通过对这个点的优化可以节省很多计算时间。
  • 小优化如下:
/*** @author liaojiamin* @Date:Created in 10:57 2021/6/9*/
public class FindUglyNumber {public static void main(String[] args) {Long time_2 = System.currentTimeMillis();System.out.println(getNumberOfUglyNumber_2(1500));System.out.println(System.currentTimeMillis() - time_2);}/*** 方法三:不逐个判断,我们先将1 是基础丑数放入已经查找数组中,此时最大丑数是1 记为max,* 因为 uglyNum * (2/3/5) 还是uglyNum,我们直接找正好大于 当前max的最小丑数,* 此时将1 分别* (2/3/5) 得到 (2/3/5),其中最小值2,此时 max = 2* 以此类推* */public static Integer getNumberOfUglyNumber_2(Integer position){if(position <= 0){return -1;}Integer[] uglyArray = new Integer[position];uglyArray[0] = 1;Integer nowPosition = 1;Integer max = uglyArray[0];while (nowPosition < position){Integer newMax = findMinUgly(max, uglyArray, nowPosition);max = newMax;uglyArray[nowPosition] = newMax;nowPosition++;}return uglyArray[position - 1];}public static Integer findMinUgly(Integer max, Integer[] uglyArray, Integer nowPosition){Integer min = Integer.MAX_VALUE;for (int i = 0; i< nowPosition ; i++) {Integer temp_5 = uglyArray[i] * 5;if(temp_5 < max){continue;}if(temp_5 > max && temp_5 < min){min = temp_5;}Integer temp_2 = uglyArray[i] * 2;if(temp_2 > max && temp_2 < min){min = temp_2;}Integer temp_3 = uglyArray[i] * 3;if(temp_3 > max && temp_3 < min){min = temp_3;}}return min;}
}
  • 经过如上一个小的优化点,可以将1500 位的查找控制在30 毫秒以内

上一篇:数据结构与算法–将数组排成最小的数
下一篇:数据结构与算法–第一个只出现一次的字符

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310363.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法--第一个只出现一次的字符

第一个只出现一次的字符 题目&#xff1a;在字符串中找出第一个只出现一次的字符&#xff0c;比如输入“wersdfxvsdfwer”&#xff0c;则输出x。 方法一&#xff1a; 还是老规矩&#xff0c;初始想法是从头遍历每一个字符&#xff0c;每遍历一个字符都与后面n-1个字符比较如果…

使用 docker 编译运行 abp 项目

在前面的两篇文章中&#xff0c;介绍了如何在华为鲲鹏架构及其Euler系统上运行dotnet core, 使用docker运行了默认的mvc模板项目&#xff0c;这篇文章继续介绍在docker中运行更复杂的dotnet core项目&#xff0c;这里以业内鼎鼎大名的abp vnext框架&#xff0c;版本 2.6 为例。…

数据结构与算法--数组中的逆序对

题目&#xff1a;在数组中的两个数字如果签名一个数字大于后面的数组&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 案例&#xff1a;输入数组{7,5&#xff0c;6,4}中一共有5个逆序对分别是{7,6}&#xff0c;{7,5}&#x…

用了这么多年的泛型,你对它到底有多了解?

现代程序员写代码没有人敢说自己没用过泛型&#xff0c;这个泛型模板T可以被任何你想要的类型替代&#xff0c;确实很魔法很神奇&#xff0c;很多人也习以为常了&#xff0c;但就是这么有趣的泛型T底层到底是怎么帮你实现的&#xff0c;不知道有多少人清楚底层玩法&#xff0c;…

数据结构与算法--两个链表中第一个公共节点

链表中第一个公共节点 公节点定义&#xff1a;同一个节点在两个链表中&#xff0c;并不是节点值相同题目&#xff1a;输入两个节点&#xff0c;找出他们的第一个公共节点&#xff0c;节点定义如需 /*** 链表元素节点** author liaojiamin* Date:Created in 12:17 2021/3/5*/ …

ASP.NET Core技术研究-全面认识Web服务器Kestrel

因为IIS不支持跨平台的原因&#xff0c;我们在升级到ASP.NET Core后&#xff0c;会接触到一个新的Web服务器Kestrel。相信大家刚接触这个Kestrel时&#xff0c;会有各种各样的疑问。今天我们全面认识一下ASP.NET Core的默认Web服务器Kestrel。一、初识Kestrel首先&#xff0c;K…

数据结构与算法--二叉堆(最大堆,最小堆)实现及原理

二叉堆&#xff08;最大堆&#xff0c;最小堆&#xff09;实现及原理 二叉堆与二叉查找树一样&#xff0c;堆也有两个性质&#xff0c;即结构性质和堆性质。和AVL树一样&#xff0c;对堆的一次操作必须到堆的所有性质都被满足才能终止&#xff0c;也就是我们每次对堆的操作都必…

Blazor WebAssembly 3.2.0 已在塔架就位 将发射新一代前端SPA框架

最美人间四月天&#xff0c;春光不负赶路人。在充满无限希望的明媚春天里&#xff0c;一路风雨兼程的.NET团队正奋力实现新的突破。根据计划&#xff0c;新一代基于WebAssembly 技术研发的前端SPA框架Blazor 将于5月19日在微软Build大会升空。目前&#xff0c;Blazor 的测试工作…

数据结构与算法--最小的k个数

最小的k个数 题目&#xff1a;输入n个整数&#xff0c;找出其中最小的k个数&#xff0c;例如输入4,5&#xff0c;6,7&#xff0c;8,9这六个数字&#xff0c;则最小的4个是4,5&#xff0c;6,7 方案一 还是最直观的方法&#xff0c;先排序&#xff0c;最快的是快排O(nlog2n)&a…

如何将 Azure 上的 Ubuntu 19.10 服务器升级到 20.04

点击上方蓝字关注“汪宇杰博客”导语Ubuntu 20.04 LTS 已经正式推出了。作为一名软粉&#xff0c;看到新版鲍叔毒瘤&#xff0c;我当然是激动万分&#xff0c;抱着批判的态度&#xff0c;第一时间很不情愿的更新了我的服务器。4月23日发布的 Ubuntu 20.04 是个 LTS 版。其 Linu…

死磕 Java 8 的日期处理

TIME Java 8 推出了全新的日期时间API并且已经很久了&#xff0c;因为业务中遇到的时间处理的还是不多&#xff0c;因此用的也少&#xff0c;而且大多是用封装好的时间共计包&#xff0c;就更少接触java8 的时间类型API了&#xff0c;因此对他不是很熟&#xff0c;今天遇到了一…

我想快速给WPF程序添加托盘菜单

我想...1 简单要求&#xff1a;使用开源控件库在XAML中声明托盘菜单&#xff0c;就像给控件添加ContextMenu一样封装了常用命令&#xff0c;比如&#xff1a;打开主窗体、退出应用程序等TerminalMACS我在TerminalMACS中添加了托盘菜单&#xff0c;最终实现的托盘菜单效果&#…

【半译】在ASP.NET Core中创建内部使用作用域服务的Quartz.NET宿主服务

在我的上一篇文章《在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度》&#xff0c;我展示了如何使用ASP.NET Core创建Quartz.NET托管服务并使用它来按计划运行后台任务。不幸的是&#xff0c;由于Quartz.NET API的工作方式&#xff0c;在Quartz作业中使用Scoped依…

mysql技术分享-- 视图是什么

视图 最近遇到mysql锁相关问题&#xff0c;在查阅资料时候&#xff0c;经常能看到在锁的解释中总有视图的概念出现&#xff0c;因此我觉得有必要先去了解一下视图相关的详细信息&#xff0c;有助于我对mysql锁相关的理解。视图&#xff08;View&#xff09;是一个命名的虚拟表…