Ugly Number
问题描述:只含有素因子2,3,5的数,称为Ugly Number,并且认为1是Ugly Number。eg. 1,2,3,4,5,6,8,9,10,12,…
问题1
给定一个数num,判断其是否为Ugly Number,是则返回true,否则返回false。
分析:判断这个数是否可以被2或者3或者5整除,并且循环判断,直到结果为1,则则这个数是Ugly Number,返回true,否则返回false。
public boolean isUglyNumber(int m) {
if(m <= 0) {
return false;
}
while(m != 1) {
if(m % 2 == 0) {
m /= 2;
} else if(m % 3 == 0) {
m /= 3;
} else if(m % 5 == 0) {
m /= 5;
} else {
return false;
}
}
return true;
}
显然,这个算法的时间复杂度是O(logN)。
问题2
给定一个数n,返回Ugly Number列表的第n个数。Ugly Number列表为1,2,3,4,5,6,8,9,10,12,15,....。eg.
n为1时,返回1;
n为7时,返回8;
n为10时,返回12;
…
题目链接:http://www.lintcode.com/zh-cn/problem/ugly-number-ii/
分析:
方法1:可以使用一个循环,从1开始,判断每一个数是不是Ugly Number,直到得到第n个数。
public int nthUglyNumber(int n) {
int sum = 0;
int i = 1;
while(sum < n) {
if(isUglyNumber(i)) {
sum++;
}
i++;
}
return i - 1;
}
显然,这个方法的时间复杂度是O(N*logN)。在lintCode上提交的时候,会发现超过时间限制,所以还需要更好的方法。
方法2:可以换一个思路,我们可以用一个数组,来手动生成一个Ugly Number的列表。就如同Fibonacci数列一样,后边的数字,总是可以由前边的数字得到。
假设A是一个长度为n的int数组,起始的时候A[0]=1,其它元素均为default值0。后边的元素均可以通过A[0]和因子2,3,5相乘来得到。
第一步: A[0]=1;
第二步: 取min(2*A[0], 3*A[0], 5*A[0])即2*A[0]=2作为A[1]。此时,A[0]=1,A[1]=2,则后边的元素可以通过A[0]和A[1]和因子2,3,5相乘来得到;
第三步: 由于在第二步中2*A[0]已经作为了A[1],所以,2的因子的数组下标应该加1,即由A[0]变为A[1],取min(2*A[1], 3*A[0], 5*A[0])即3*A[0]=3作为A[2]。此时,A[0]=1,A[1]=2,A[2]=3。后边的元素可以通过A[0],A[1],A[2]和因子2,3,5相乘来得到;
第四步: 以此类推,A[3]=min(2*A[1], 3*A[1], 5*A[0])=2*A[1]=4;
第五步: A[4]=min(2*A[2], 3*A[1], 5*A[0])=5*A[0]=5;
第六步: A[5]=min(2*A[2], 3*A[1], 5*A[1])=6,注意,此时2*A[2]和3*A[1]的值均为最小值6,所以在下一步中,2和3的因子的数组下标都应该加1,即2*A[3]和3*A[2];
第七步: A[6]=min(2*A[3], 3*A[2], 5*A[1])=2*A[3]=8;
…
以此类推,得到A[n-1]即为题目所求。
public int nthUglyNumber(int n) {
if(n <= 0) {
return 0;
}
int [] nums = new int[n];
nums[0] = 1;
int index2 = 0, index3 = 0, index5 = 0;
for(int i = 1; i < n; i++) {
nums[i] = Math.min(2 * nums[index2], Math.min(3 * nums[index3], 5 * nums[index5]));
if (nums[i] == 2 * nums[index2]) {
index2++;
}
if (nums[i] == 3 * nums[index3]) {
index3++;
}
if(nums[i] == 5 * nums[index5]) {
index5++;
}
}
return nums[n - 1];
}
显然,方法2的时间复杂度为O(N)。
问题3
Super Ugly Number,给定一个素数数组primes和一个正整数n,求使用这个数组构建的Ugly Number的第n个数。这个问题是问题2的一般化,在问题2中,相当与primes是{2,3,5}。
题目链接:http://www.lintcode.com/zh-cn/problem/super-ugly-number/
分析: 与问题2的第二种思路一样,这里使用一个数组来保存下标,相当于上面的index2,index3,index5。
/**
* @param n a positive integer
* @param primes the given prime list
* @return the nth super ugly number
*/
public int nthSuperUglyNumber(int n, int[] primes) {
if(n <= 0) {
return 0;
}
int []nums = new int[n];
int []index = new int[primes.length];
nums[0] = 1;
for(int i = 1; i < n; i++) {
//find min number as nums[i]
int min = primes[0] * nums[index[0]];
for(int j = 1; j < primes.length; j++) {
if(primes[j] * nums[index[j]] < min) {
min = primes[j] * nums[index[j]];
}
}
nums[i] = min;
//update index
for(int j = 0; j < primes.length; j++) {
if(min == primes[j] * nums[index[j]]) {
index[j]++;
}
}
}
return nums[n - 1];
}
假设primes的length为M,显然,这个方法的时间复杂度是O(N*M)。