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<7){
return n;
}
int[] arr = new int[n+1];
arr[1] = 1;
//丑数数组的索引
int p2=1, p3=1, p5=1;
for(int i=2; i<=n; i++){
arr[i] = Math.min(arr[p2]*2, Math.min(arr[p3]*3, arr[p5]*5));
if(arr[p2]*2==arr[i]){
p2++;
}
if(arr[p3]*3==arr[i]){
p3++;
}
if(arr[p5]*5==arr[i]){
p5++;
}
}
return arr[n];
}
}
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<n时, 2^m < 2^n; 3^m<3^n; 5^m<5^n
- 不要在脑中比较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;
}
}