「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」
题目
面试题 17.09. 第 k 个数
有些数的素因子只有 3,5,7,请设计一个算法找出第 k 个数。注意,不是必须有这些素因子,而是必须不包含其他的素因子。例如,前几个数按顺序应该是 1,3,5,7,9,15,21。
理解题目意思:
这题乍一看,没有弄明白是怎么一回事,翻了一下题解,大概看了下,才弄明白。
素因子,换句话说就是质因子或者质因数,它能整除一些给定的整数。
用大白话解释这个题解就是,有这么一些数,他们是由若干个3、5、7相乘得到的,比如3x3=9、3x5=15、3x7=21、5x5=25、7x7=49、3x5x7=105, 这些数都能满足这个题解的要求。
那么我们要做的就是用一个算法,去把符合这个要求的数,从小到大排成一个数列,然后去找第k个数是几。
思路:
理解这题,首先要明白这个数列递推的过程,数列的每一项都是由它前面的所有项x3或5或7得到的所有数中最小的一个,
let arr = [1, 3, 5, 7, 9, 15, 21, 25, 27, 35]
我们看下面这个数列
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
| arr[1] | 1x3 | 1x5 | 1x7 |
| arr[2] | 3x3 | 3x5 | 3x7 |
| arr[3] | 5x3 | 5x5 | 5x7 |
| arr[4] | 7x3 | 7x5 | 7x7 |
| arr[5] | 9x3 | 9x5 | 9x7 |
| arr[6] | 15x3 | 15x5 | 15x7 |
| arr[7] | 21x3 | 21x5 | 21x7 |
暴力一点的理解
arr[1]是从表格中它右边3个数里取最小的,得1x3
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
| arr[1] | 1x3 | 1x5 | 1x7 |
arr[2]是从表格中它右边6个数里取最小的,得1x5
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
| arr[1] | 1x5 | 1x7 | |
| arr[2] | 3x3 | 3x5 | 3x7 |
arr[3]是从表格中它右边9个数里取最小的,得1x7
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
| arr[1] | 1x7 | ||
| arr[2] | 3x3 | 3x5 | 3x7 |
| arr[3] | 5x3 | 5x5 | 5x7 |
......
每找一个数,我们就往一个暂存数组里推三个数,然后从这个暂存数组里找最小的,由于可能出现相同的数,比如3x5和5x3,所以需要做去重处理。
根据这个理解其实我们就能写出一个超级暴力解法
/**
* @param {number} k
* @return {number}
*/
var getKthMagicNumber = function(k) {
// 分别是三个数列的指针
let p1 = 0,p2 = 0,p3 = 0;
// 结果数组
let res = [1];
// 用来存放剩余未取出的数的比较数组
let temp = [];
// 暂存最小值
let min = 1;
for(let i=0;i<k;i++){
// 把三条队列所有的数都依次推进这个比较数组
temp.push(res[p1]*3)
temp.push(res[p2]*5)
temp.push(res[p3]*7)
// 数组去重
temp = unique(temp);
// 指针++,下一波循环用来放下一组数据
p1++;
p2++;
p3++;
// 从temp中取出最小的值
for(let j=0;j<temp.length;j++){
if(temp[j]<temp[j+1]){
[temp[j],temp[j+1]] = [temp[j+1],temp[j]]
}
}
min = temp[temp.length-1];
// 拿出后,将这个值删掉
temp.pop();
res.push(min);
}
return res[k-1];
};
// 数组去重
function unique (arr) {
return Array.from(new Set(arr))
}
这个解法相当暴力,时间复杂度为O(k2)
实际上,我们分析,其实可以找到一些规律
第一次比较,是从3,5,7中找,没有异议
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
| 第一次比较 | 1x3 | 1x5 | 1x7 |
第二次比较,3被取走了,理论上是从5,7,9,15,21中找,但由于3x5肯定大于1x5,3x7肯定大于1x7,
所以实际比较9,5,7就可以了
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
1x5 | 1x7 | ||
| 第二次比较 | 3x3 | 3x5 | 3x7 |
同理,第三次比较
| arr | arr[i]x3 | arr[i]x5 | arr[i]x7 |
|---|---|---|---|
1x7 | |||
3x3 | 3x5 | 3x7 | |
| 第三次比较 | 5x3 | 5x5 | 5x7 |
那么其实就总结出一个规律
每次比较,并不需要像刚才暴力解法那样,把所有的数都比较一遍,其实只用比较3个数就行了
我暂且定义这个3个数构成的数组叫pk台
假设p1、p2、p3分别为3、5、7数列当前的指针
那么其实每次进入pk台的是这个三个数,arr[p1]x3、arr[p2]x5、arr[p3]x7
他们谁从pk台中踢出去了,则让他们这一列的下一个大哥进来比
而并不需要让他们所有人的大哥都来,因为有的小弟还没比完呢,大哥根本就不需要参与
那么我们实现代码如下:
/**
* @param {number} k
* @return {number}
*/
var getKthMagicNumber = function(k) {
let p1 = 0,p2 = 0,p3 = 0;
let res = [1];
let min = 1;
for(let i=0;i<k;i++){
min = Math.min(res[p1]*3,res[p2]*5,res[p3]*7);
res.push(min);
if(min == res[p1]*3) p1++;
if(min == res[p2]*5) p2++;
if(min == res[p3]*7) p3++;
}
return res[k-1];
};