[路飞]_leetcode刷题_面试题 17.09. 第 k 个数

215 阅读4分钟

「这是我参与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]

我们看下面这个数列

arrarr[i]x3arr[i]x5arr[i]x7
arr[1]1x31x51x7
arr[2]3x33x53x7
arr[3]5x35x55x7
arr[4]7x37x57x7
arr[5]9x39x59x7
arr[6]15x315x515x7
arr[7]21x321x521x7

暴力一点的理解

arr[1]是从表格中它右边3个数里取最小的,得1x3

arrarr[i]x3arr[i]x5arr[i]x7
arr[1]1x31x51x7

arr[2]是从表格中它右边6个数里取最小的,得1x5

arrarr[i]x3arr[i]x5arr[i]x7
arr[1]1x31x51x7
arr[2]3x33x53x7

arr[3]是从表格中它右边9个数里取最小的,得1x7

arrarr[i]x3arr[i]x5arr[i]x7
arr[1]1x31x51x7
arr[2]3x33x53x7
arr[3]5x35x55x7

......

每找一个数,我们就往一个暂存数组里推三个数,然后从这个暂存数组里找最小的,由于可能出现相同的数,比如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中找,没有异议

arrarr[i]x3arr[i]x5arr[i]x7
第一次比较1x3 1x51x7

第二次比较,3被取走了,理论上是从5,7,9,15,21中找,但由于3x5肯定大于1x5,3x7肯定大于1x7,

所以实际比较9,5,7就可以了

arrarr[i]x3arr[i]x5arr[i]x7
1x31x51x7
第二次比较3x3 3x53x7

同理,第三次比较

arrarr[i]x3arr[i]x5arr[i]x7
1x31x51x7
3x33x5 3x7
第三次比较5x35x55x7

那么其实就总结出一个规律

每次比较,并不需要像刚才暴力解法那样,把所有的数都比较一遍,其实只用比较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];
};