zoukankan      html  css  js  c++  java
  • 剑指Offer丑数问题

    这是剑指第一次卡死我的题……记录一下

    首先看题目:

    把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

    一开始的思路:

    一开始,我就直接蛮力,就按照它的描述,从2到根号n一个个试,看看是不是它的因子,是的话是不是质数,是质数的话是不是2或者3或者5什么的。

    最后做出来,前面几个数字都是可以通过测试的,但提交就一直说我超时通过率为0……然后本地测了下超时的用例1500——emmm确实是,算了半天都没出来。

    然后百度,百度百科上对丑数的判别:

    首先除2,直到不能整除为止,然后除5到不能整除为止,然后除3直到不能整除为止。最终判断剩余的数字是否为1,如果是1则为丑数,否则不是丑数。
     
    用这个思路又写了个,从2开始往上+1,然后每个都这样判断是不是丑数,好的还是超时。(其实这个和我的蛮力一样,只是我的蛮力比较恶心。)
     
     
    最后没办法看讨论,大多用的都是一个思路:
    所有的丑数都是前面的那个丑数乘以2或者3或者5而来的。所以我们可以维护三个队列和一个正式丑数序列:
    链接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b
    来源:牛客网

    (1)丑数数组: 1
    乘以2的队列:2
    乘以3的队列:3
    乘以5的队列:5
    选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
    (2)丑数数组:1,2
    乘以2的队列:4
    乘以3的队列:3,6
    乘以5的队列:5,10
    选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
    (3)丑数数组:1,2,3
    乘以2的队列:4,6
    乘以3的队列:6,9
    乘以5的队列:5,10,15
    选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
    (4)丑数数组:1,2,3,4
    乘以2的队列:6,8
    乘以3的队列:6,9,12
    乘以5的队列:5,10,15,20
    选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
    (5)丑数数组:1,2,3,4,5
    乘以2的队列:6,8,10,
    乘以3的队列:6,9,12,15
    乘以5的队列:10,15,20,25
    选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;

     然后我按照这个思路,自己实现了次,也遇到了很多问题……

    我是真正地维护了三个队列,然后每次把最新的丑数乘以2,3,5然后分别加到三个队列中,然后取出最小的数字处理,从哪个队列中取出就哪个队列出队。队列直接用PriorityQueue,可以直接获得最小值。

    遇到的问题是:

      1. 我这个直接按照上面的思路做的话,到后面数字很大会溢出……然后溢出的话,比如一个很大的丑数*5溢出了,就变成一个负数然后以后的最小值就是这个负数了——我的解决方法是:重写一个Compator,然后传入PriorityQueue中去。

      2. 这还不够,因为到了后面,可能出现一个队列中的所有数字都小于0的情况,所以在拿出数字后比较出最小的这一步也要考虑到负数的情况。

    看下我的这个思路的实现的代码,已经通过测试:

    import java.util.*;
    
    public class Solution {
        public int GetUglyNumber_Solution(int index) {
            if(index <= 0)return 0;
            if(index == 1)return 1;
            
            Queue<Integer> q2 = new PriorityQueue<Integer>(new NewComparator());
            Queue<Integer> q3 = new PriorityQueue<Integer>(new NewComparator());
            Queue<Integer> q5 = new PriorityQueue<Integer>(new NewComparator());
            
            int uglyNum = 1, count = 1, minTemp;
            while(true) {
                q2.add(uglyNum * 2);
                q3.add(uglyNum * 3);
                q5.add(uglyNum * 5);
                
                //下面的操作这么麻烦是因为会有几个队列有着同样的最小值的情况,这时候都要让它出队
                minTemp = getMinOfThree(q2.peek(), q3.peek(), q5.peek(), count);
                
                if(q2.peek() == minTemp)q2.poll();
                if(q3.peek() == minTemp)q3.poll();
                if(q5.peek() == minTemp)q5.poll();
                
                uglyNum = minTemp;
                count++;
                if(count == index)return uglyNum;
            }
        }
        
        
        // 找三个数字中没有溢出的最小值
        private int getMinOfThree(int a, int b, int c, int count) {
    
            if (a < 0 || b < 0 || c < 0) {// 存在溢出情况,有元素小于0
                int[] temp = new int[3];
                temp[0] = a;
                temp[1] = b;
                temp[2] = c;
                Arrays.sort(temp);
                
                for (int x : temp) {
                    if (x < 0)
                        continue;
                    return x;
                }
            }
            return a < b ? (a < c ? a : c) : (b < c ? b : c);
        }
        
        //因为之前的那个,如果有数字溢出了,就会变成负数,负数肯定最小然后就会被poll出来,最后结果就会有错误
        private class NewComparator implements Comparator<Integer> {
    
            @Override
            public int compare(Integer o1, Integer o2) {
                // TODO Auto-generated method stub
                if(o1 < 0)return 1;
                if(o1 < o2)return -1;
                else if(o1 == o2)return 0;
                else return 1;
            }
            
        }
        
    }

    但其实我们可以不用维护三个队列:

    链接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b
    来源:牛客网

    们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;
    (1)1
    |2
    |3
    |5
    目前指针指向0,0,0,队列头arr[0] * 2 = 2,  arr[0] * 3 = 3,  arr[0] * 5 = 5
    (2)1 2
    2 |4
    |3 6
    |5 10
    目前指针指向1,0,0,队列头arr[1] * 2 = 4,  arr[0] * 3 = 3, arr[0] * 5 = 5
    (3)1 2 3
    2| 4 6
    3 |6 9
    |5 10 15
    目前指针指向1,1,0,队列头arr[1] * 2 = 4,  arr[1] * 3 = 6, arr[0] * 5 = 5
     
     
    上一个简化后不用维护三个队列的Java的实现代码:
    public int GetUglyNumber_Solution2(int index) {
            if (index <= 0)
                return 0;
            ArrayList<Integer> list = new ArrayList<Integer>();
            // add进第一个丑数1
            list.add(1);
            // 三个下标用于记录丑数的位置
            int i2 = 0, i3 = 0, i5 = 0;
            while (list.size() < index) {
                // 三个数都是可能的丑数,取最小的放进丑数数组里面
                int n2 = list.get(i2) * 2;
                int n3 = list.get(i3) * 3;
                int n5 = list.get(i5) * 5;
                int min = Math.min(n2, Math.min(n3, n5));
                list.add(min);
                if (min == n2)
                    i2++;
                if (min == n3)
                    i3++;
                if (min == n5)
                    i5++;
            }
            return list.get(list.size() - 1);
        }

    可以看见,就是三个指针在动。而且这里似乎不会出现溢出有负数的情况,因为我那里每次是用最新的丑数去乘2,3,5;而这里是用之前的丑数,就i2,i3,i5指针所在的那个丑数来乘,所以好很多。

  • 相关阅读:
    ChartControl第一课简短的控件初步设计
    DevExpress中ChartControl柱状图(Bar)用法
    devexpress中用ChartControl生成柱状图
    ChartControl一个小Demo
    初识Devexpress ChartControl 之 动态添加stepline及TextAnnotation
    DEV控件之ChartControl用法
    DevExpress]ChartControl 创建Drill-Down样式的Title
    winform CheckedListBox实现全选/全不选
    winform 配置文件的加密解密
    Winform自定义窗体样式,实现标题栏可灵活自定义
  • 原文地址:https://www.cnblogs.com/wangshen31/p/10649245.html
Copyright © 2011-2022 走看看