剑指offer面试题49(java版):丑数

welcome to my blog

剑指offer面试题49(java版):丑数

寻找丑数 滴滴出行2018校园招聘网申笔试-智能交互技术研发工程师(第一批) 中等

题目描述

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

第四次做; 核心: 1) 明确p2,p3,p5的意义, 这三个指针都是丑数数组的索引, pi*i是下一个可能的丑数, p2指向的丑数乘2, p3指向的丑数乘3, p5指向的丑数乘5

class Solution {
    public int nthUglyNumber(int n) {
        if(n<=6)
            return n;
        // arr[1]表示第一个丑数
        int[] arr = new int[n+1];
        arr[1] = 1;
        //p2,p3,p5表示索引, 指向某一个丑数; p2指向的丑数乘2, p3指向的丑数乘3, p5指向的丑数乘5
        int p2=1, p3=1, p5=1;
        int curUgly = 6;
        for(int i=2; i<=n; i++){
            //下一个可能的丑数
            int min = Math.min(arr[p2]*2, arr[p3]*3);
            min = Math.min(min, arr[p5]*5);
            
            //添加最新的丑数
            arr[i] = min;
            
            //找出丑数的来源, 更新对应的指针, 更新后的索引指向最新的丑数
            if(arr[p2]*2==min)
                p2++;
            if(arr[p3]*3==min)
                p3++;
            if(arr[p5]*5==min)
                p5++;
        }
        return arr[n];
    }
}

第三次做, 感觉对这题理解的不深入; 用一个数组存丑数, 维护三个指针, 每次从三个候选丑数中选出最小的作为当前的丑数,并更新对应的指针; 在纸上写写更新过程

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=6)
            return index;
        int[] arr = new int[index+1];
        arr[1] = 1;
        int p2=1, p3=1, p5=1, currMin;
        for(int i=2; i<=index; i++){
            currMin = Math.min(arr[p2]*2, arr[p3]*3);
            currMin = Math.min(currMin, arr[p5]*5);
            arr[i] = currMin;
            //update
            if(arr[p2]*2==currMin)
                p2++;
            if(arr[p3]*3==currMin)
                p3++;
            if(arr[p5]*5==currMin)
                p5++;
        }
        return arr[index];
    }
}

第二次做,用一个数组存储丑数,维护三个索引分别用来指示2,3,5这三个数和哪个arr[?]相乘

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=6)
            return index;
        //
        //数组中加入的是丑数
        int[] arr = new int[index+1];
        int curr = 1;
        arr[1] = 1;
        /*
        维护三个索引;
        如果新丑数是arr[index2]*2,那么下一轮循环中和2相乘的是arr[index2+1],所以需要更新索引,index2++
        如果新丑数是arr[index3]*3,那么下一轮循环中和3相乘的是arr[index3+1],所以需要更新索引,index3++
        如果新丑数是arr[index5]*5,那么下一轮循环中和5相乘的是arr[index5+1],所以需要更新索引,index5++
        */
        int index2=1, index3=1, index5=1;
        for(int i=2; i<=index; i++){
            //找出当前要进行比较的三个数中最小的数,也就是新丑数
            curr = Math.min(Math.min(arr[index2]*2, arr[index3]*3), arr[index5]*5);
            //将新丑数加入数组
            arr[i] = curr;
            //更新索引
            if(arr[index2]*2 == curr)
                index2++;
            if(arr[index3]*3 == curr)
                index3++;
            if(arr[index5]*5 == curr)
                index5++;
        }
        return arr[index];
    }
}

第二次做,维护三个ArrayList,需要理解并记住

import java.util.ArrayList;

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<1)
            return 0;
        if(index==1)
            return 1;
        int currUgly = 1, count = 1, tempMin = -1;
        ArrayList<Integer> al2 = new ArrayList<>();
        ArrayList<Integer> al3 = new ArrayList<>();
        ArrayList<Integer> al5 = new ArrayList<>();
        int index2=0, index3=0, index5=0;
        
        while(count != index){
            //将当前丑数分别乘2,3,5后的结果加入队列中
            al2.add(currUgly * 2);
            al3.add(currUgly * 3);
            al5.add(currUgly * 5);
            //选出最小值作为新的丑数
            tempMin = Math.min(al2.get(index2), al3.get(index3));
            tempMin = Math.min(tempMin, al5.get(index5));
            currUgly = tempMin;
            //丑数计数加1
            count++;
            //更新索引,作用:考虑过的丑数就不再考虑了
            if(al2.get(index2)==tempMin)
                index2++;
            if(al3.get(index3)==tempMin)
                index3++;
            if(al5.get(index5)==tempMin)
                index5++;
        }
        return currUgly;
    }
}

暴力解法(不满足时间复杂度要求)

  • 主要是巩固基础
  • int i,j=1;这种写法只是对j进行了初始化!!!
  • 不要在循环内部直接操作循环变量, 如果需要根据循环变量进行操作的话, 可以先把循环变量赋值给temp
  • 满足temp%2==0后再进行temp/2的操作
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        //先来个暴利的
        int count=0;
        int curr=1,temp = 1;
        while(true){
            temp = curr;
            while(temp%2==0)
                temp /= 2;
            while(temp%3==0)
                temp /= 3;
            while(temp%5==0)
                temp /= 5;
            if(temp==1)
                count++;
            if(count==index)
                break;
            curr++;
        }
        return curr;
    }
}

维护三个队列

思路

  • 丑数=2^x*3^y*5^z, x,y,z属于{0,1,2,3,…}
  • 从第一个丑数1开始,1分别与2,3,5相乘得到三个新丑数, 三个新丑数中最小2的加入丑数队列, 在下一次比较中不再考虑2
  • 2分别与2,3,5相乘得到三个新丑数, 由于上一轮比较中, 1与3,5相乘的结果没有参与比较, 同时2与3,5相乘的结果一定大于1与3,5相乘的结果, 所以本轮比较中用2与2相乘的结果,1与3,5相乘的结果,这三个结果进行比较, 取出最小的数加入丑数队列, 再下一次比较中不再考虑该丑数
import java.util.ArrayList;

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        //维护三个数组, 分别是丑数乘2,乘3,乘5得到的结果
        ArrayList<Integer> ugly = new ArrayList<Integer>();
        ArrayList<Integer> mul2 = new ArrayList<Integer>();
        ArrayList<Integer> mul3 = new ArrayList<Integer>();
        ArrayList<Integer> mul5 = new ArrayList<Integer>();
        int p=0, index2=0, index3=0, index5=0;
        if(index<7)
            return index;
        ugly.add(1);
        int min;
        while(ugly.size() != index){
            //每一轮循环中做什么? 根据当前丑数,算出该丑数分别乘2,3,5的结果并分别添加进mul2,mul3,mul5中; 再找出mul2,mul3,mul5中指针指向最小的元素, 添加到ugly中
            mul2.add(ugly.get(p)*2);
            mul3.add(ugly.get(p)*3);
            mul5.add(ugly.get(p++)*5);
            //
            min = mul2.get(index2);
            if(min>mul3.get(index3))
                min = mul3.get(index3);
            if(min>mul5.get(index5))
                min = mul5.get(index5);
            
            ugly.add(min);
            
            if(mul2.get(index2)==min)
                index2++;
            if(mul3.get(index3)==min)
                index3++;
            if(mul5.get(index5)==min)
                index5++;
        }
        return ugly.get(ugly.size()-1);
    }
}

不用维护3个队列,维护一个数组即可

思路

  • 维护一个数组即可, 该数组中是逐个添加进来的丑数, 每一次循环增加一个丑数
  • 但是要维护三个索引,用来表示当前数字(2,3,5)和哪个丑数相乘. 这样做的原因是, 当m
  • 不要在脑中比较2^x与3^y的大小, 因为x,y不确定, 导致比较的结果也不确定
  • 主要是理解代码中三个if的作用
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        //input check
        if(index<=6)
            return index;
        //execute
        int[] arr = new int[index];
        int p=-1, p2=0, p3=0, p5=0, res=0;
        arr[++p]=1;
        while(p!=index){
            if(p==index-1)
                break;
            res = min(arr[p2]*2, min(arr[p3]*3, arr[p5]*5));
            arr[++p] = res;
            /*下面三个if的作用: 如果当前的丑数乘arr[p5]没有进入arr数组, 就没有必要使用下一个丑数乘arr[p5]了
            因为下一个丑数乘arr[p5]一定比当前丑数乘arr[p5]大!
            */
            if(res == arr[p2]*2)
                p2++;
            if(res == arr[p3]*3)
                p3++;
            if(res == arr[p5]*5)
                p5++;
        }
        return arr[index-1];
    }
    public int min(int a, int b){
            return a<b?a:b;
    }
}

你可能感兴趣的:(剑指offer,剑指offer)