题目来源:https://leetcode.cn/problems/get-kth-magic-number-lcci/
大致题意:
返回素因子只能是 3 5 7 的数列的第 k 个数,这个数列前几个数为:1、3、5、7、9、15
思路
已知数列每个数的素因子只有 3 5 7,那么该数列的数在不为 1 时只能是 3 5 7 相乘组成的数
于是可以将当前已知的数列中的数放入 TreeSet 中,每次遍历取出当前最小的数,然后将当前数与 3 5 7 的乘积放入 set,重复这个操作直至取出第 k 个数
具体过程为:
- 使用一个TreeSet存下当前数列中的数,在遍历过程中不断更新;使用一个索引位 idx 表示当前取出的数是第几个
- 在 idx 小于 k 时,取出当前TreeSet中最小的数,然后将取出的数分别乘以 3、5、7,将乘积放入TreeSet中,这样保证了数列的下一位数一直在TreeSet中,同时集合也会自动去重
代码:
public int getKthMagicNumber(int k) {
int[] nums = new int[]{3, 5, 7};
TreeSet<Long> set = new TreeSet<>();
set.add(1L); // 初始时放入 1
int idx = 0; // 当前取出了几个数
int res = 0; // 存最新的取出的数
while (idx < k) {
long cur = set.first(); // 取出当前最小的数
res = (int) cur; // 更新结果
for (int i = 0; i < 3; i++) { // 放入当前数与 3 5 7 的乘积
set.add(cur * nums[i]);
}
idx++; // 更新标记
}
return res;
}
三指针优化
上述方法的时间复杂度为 O(klogk),主要是TreeSet的排序时长
观察数列中的数可以发现,数列较大的数都是较小的数与 3 5 7 的乘积,可以知道下一个数是数列中已经出现数与 3 5 7 某个数的乘积
并且可以发现若数列出现过 nums[i] * 3 的数(这里假设 nums[i] 为数列第 i 个数),则接下来再出现的与 3 相乘的数会是 nums[i + 1] * 3,存在递增的关系,即可以通过指针记录当前与因子 3 相乘的数应该为数列的第几个数
于是可以通过一个数组记录数列中已经出现的数,并且用三个指针记录当前与 3 5 7 相乘的数的索引。每次求出对应索引与 3 5 7 的乘积,然后最小的乘积即为当前位置数列中的数,同时更新最小乘积对应指针即可。
具体解题思路为:
- 初始时,三个指针都为 1,数组第 1 位为 1,使用标记位 idx 表示当前计算到第 idx 个数
- 每次遍历,求出当前指针对应位置的数列中的数与 3 5 7 的乘积,并求出最小乘积
- 更新最小乘积对应的指针,需要注意的是,可能有两个指针与数列中的数的乘积相同,此时需要两个指针都需要更新。比如数列中的数为 1 3 5 7 9,因子 3 5 7 对应的指针为 3 2 2,那么下一个数为 min(5 * 3, 3 * 5, 3 * 7) = 15,此时 3 和 5 对应的指针计算的乘积都为 15,两个指针都需要更新
代码:
public int getKthMagicNumber_ans(int k) {
int[] dp = new int[k + 1];
dp[1] = 1; // 放入第一个数
// 初始化三个指针
int p3 = 1, p5 = 1, p7 = 1;
// 表示当前计算到第 idx 个数
int idx = 1;
while (idx <= k) {
// 计算三个指针对应位置的数与 3 5 7 的乘积
int num3 = dp[p3] * 3, num5 = dp[p5] * 5, num7 = dp[p7] * 7;
// 当前数应该为最小乘积
dp[idx] = Math.min(Math.min(num3, num5), num7);
// 更新乘积对应的指针
if (dp[idx] == num3) {
p3++;
}
if (dp[idx] == num5) {
p5++;
}
if (dp[idx] == num7) {
p7++;
}
idx++;
}
return dp[k];
}