面试题17.09.第k个数
题意分析:
找出素因子3、5、7乘积的顺序序列的第k个数
1, 3, 5, 7, 9, 15, 21…
k = 5 ——》9
3
a
×
5
b
×
7
c
3^a \times 5^b \times 7^c
3a×5b×7c形式的数num
很自然会想到当
a
+
b
+
c
≤
k
a + b + c \le k
a+b+c≤k时,排序后的第k个数,注意对应唯一的三元组(a, b, c),num
是唯一的
这样的三元组有 ( k + 2 ) ( k + 1 ) 2 + . . . + 2 × 1 2 \frac{(k+2)(k+1)}{2} + ... + \frac{2 \times 1}{2} 2(k+2)(k+1)+...+22×1
// 时间复杂度为O(N^3logN)
public int getKthMagicNumber1(int k) {
ArrayList<Long> arr = new ArrayList<>();
for(int i = 0; i <= k; i++) {
for (int j = 0; j <= k - i; j++) {
for (int m = 0; m <= k - i - j; m++) {
long num = (long)(Math.pow(3, i) * Math.pow(5, j) * Math.pow(7, m));
arr.add(num);
}
}
}
Collections.sort(arr);
return arr.get(k - 1).intValue();
}
复杂度太高,超时
列举的三元组数量太多,得另辟蹊径
观察 3 a × 5 b × 7 c 3^a \times 5^b \times 7^c 3a×5b×7c,发现后面的数都是由前面的数 × 3 , × 5 , × 7 \times3, \times 5, \times7 ×3,×5,×7所得
那就生成序列,维持其有序性,可以用PriorityQueue
或者TreeSet
,注意去重
// 时间复杂度为O(NlogN)
public int getKthMagicNumber2(int k) {
TreeSet<Long> set = new TreeSet<>();
set.add(1L);
for(int i = 0; i < k; i++) {
long num = set.pollFirst();
set.add(num * 3);
set.add(num * 5);
set.add(num * 7);
}
return (int)set.pollFirst().longValue();
}
半山腰的风景也很美,但我们不能止步不前
基本达标,但只击败了22%的人,可以再优化
优化的点在于去重和有序,本来由前面生产后面的数都是相对有序,相对有序,再判断一下都可以自行去重了
将一个队列拆分为三个队列
1, 3, 9, 15, …
1, 5, 25, 35, …
1, 7, 21, 35, …
双向队列实现
// 时间复杂度为O(N)
public int getKthMagicNumber3(int k) {
Deque<Integer> arrNum3 = new LinkedList<>();
Deque<Integer> arrNum5 = new LinkedList<>();
Deque<Integer> arrNum7 = new LinkedList<>();
arrNum3.addLast(1);
arrNum5.addLast(1);
arrNum7.addLast(1);
int minValue = 1;
for(int i = 0; i < k; i++) {
minValue = Math.min(Math.min(arrNum3.peekFirst(), arrNum5.peekFirst()), arrNum7.peekFirst());
if (arrNum3.peekFirst() == minValue) {
arrNum3.pollFirst();
}
if (arrNum5.peekFirst() == minValue) {
arrNum5.pollFirst();
}
if (arrNum7.peekFirst() == minValue) {
arrNum7.pollFirst();
}
arrNum3.addLast(minValue * 3);
arrNum5.addLast(minValue * 5);
arrNum7.addLast(minValue * 7);
}
return minValue;
}
动态数组 + 索引
// 时间复杂度为O(N)
public static int getKthMagicNumber4(int k) {
ArrayList<Integer> arrNum3 = new ArrayList<>(k);
ArrayList<Integer> arrNum5 = new ArrayList<>(k);
ArrayList<Integer> arrNum7 = new ArrayList<>(k);
arrNum3.add(1);
arrNum5.add(1);
arrNum7.add(1);
int arrNum3Idx = 0;
int arrNum5Idx = 0;
int arrNum7Idx = 0;
int minValue = 1;
for(int i = 0; i < k; i++) {
minValue = Math.min(Math.min(arrNum3.get(arrNum3Idx), arrNum5.get(arrNum5Idx)), arrNum7.get(arrNum7Idx));
if (arrNum3.get(arrNum3Idx) == minValue) {
arrNum3Idx++;
}
if (arrNum5.get(arrNum5Idx) == minValue) {
arrNum5Idx++;
}
if (arrNum7.get(arrNum7Idx) == minValue) {
arrNum7Idx++;
}
arrNum3.add(minValue * 3);
arrNum5.add(minValue * 5);
arrNum7.add(minValue * 7);
}
return minValue;
}
数组 + 索引,避免装箱/拆箱带来的性能损耗
// 时间复杂度为O(N)
public static int getKthMagicNumber4_2(int k) {
int[] arrNum3 = new int[k + 7];
int[] arrNum5 = new int[k + 7];
int[] arrNum7 = new int[k + 7];
arrNum3[0] = 1;
arrNum5[0] = 1;
arrNum7[0] = 1;
int arrNum3Idx = 0;
int arrNum5Idx = 0;
int arrNum7Idx = 0;
int minValue = 1;
for(int i = 1; i <= k; i++) {
minValue = Math.min(Math.min(arrNum3[arrNum3Idx], arrNum5[arrNum5Idx]), arrNum7[arrNum7Idx]);
if (arrNum3[arrNum3Idx] == minValue) {
arrNum3Idx++;
}
if (arrNum5[arrNum5Idx] == minValue) {
arrNum5Idx++;
}
if (arrNum7[arrNum7Idx] == minValue) {
arrNum7Idx++;
}
arrNum3[i] = minValue * 3;
arrNum5[i] = minValue * 5;
arrNum7[i] = minValue * 7;
}
return minValue;
}
击败了100%的人,到这里差不多了,但还有最优解
在人间已是癫,何苦要上青天
空间优化,仔细再想想,这三个队列有重复值
能不能不忘初心,再将三者合二为一呢
有点像多个有序列表合并
// 时间复杂度为O(N)
public static int getKthMagicNumber5(int k) {
int[] arrNum = new int[k + 7];
arrNum[0] = 1;
int arrNum3Idx = 0;
int arrNum5Idx = 0;
int arrNum7Idx = 0;
int minValue = 1;
for(int i = 1; i < k; i++) {
minValue = Math.min(Math.min(arrNum[arrNum3Idx] * 3, arrNum[arrNum5Idx] * 5), arrNum[arrNum7Idx] * 7);
if (minValue % 3 == 0) {
arrNum3Idx++;
}
if (minValue % 5 == 0) {
arrNum5Idx++;
}
if (minValue % 7== 0) {
arrNum7Idx++;
}
arrNum[i] = minValue;
}
return minValue;
}
内存击败66%——》内存击败88%
这题目再进阶下,更改下素数因子的值,或者增加几个素数因子,有没有**普世版**?
// 时间复杂度为O(N),扩展版
public static int getKthMagicNumber5_2(int k) {
int[] arrNum = new int[k + 7];
int[] bases = {7, 5, 3};
int[] idxs = new int[bases.length];
arrNum[0] = 1;
int minValue = 1;
for(int i = 1; i < k; i++) {
minValue = Integer.MAX_VALUE;
for (int c = 0; c < bases.length; c++) {
minValue = Math.min(minValue, arrNum[idxs[c]] * bases[c]);
}
for (int c = 0; c < bases.length; c++) {
if (minValue % bases[c] == 0) {
idxs[c]++;
}
}
arrNum[i] = minValue;
}
return minValue;
}
更改素数因此列表bases
即可,填写的顺序不重要
如果是非素数因子,即因此之间有公约数,那这样以乘积生成的数就会有重复,就不能合并为一个队列,简单去重了,那就可以按照多个因数队列处理,时间复杂度为O(MN),空间复杂度为O(MN),其中M为因子个数,N为第k个数
引用