「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
说明:文章部分内容及图片出自网络,如有侵权请与我本人联系(主页有公众号:小攻城狮学前端)
作者:小只前端攻城狮、 主页:小只前端攻城狮的主页、 来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【LeetCode 264.丑数II 】- JavaScript(动态规划+最小堆)
题目描述:编写一个程序,找出第 n 个丑数。
分析:丑数就是只包含质因数 2, 3, 5 的正整数。
解法 1: 动态规划
思路:首先我们得知道因为丑数只包含质因数 2, 3, 5,这是很关键的,因此对于下个丑数来说,一定是前面某个丑数的三倍四倍或者五倍。
那我们使用三个指针 ptr2、ptr3、ptr5,它们指向的数对应235即只能乘 2、3 和 5。在循环过程中,每次选取 2 * res[ptr2]、3 * res[ptr3] 和 5 * res[ptr5]这三个数中结果最小的数,并且将对应的指针向前移动。有效循环是 n 次,当循环结束后,res 数组中就按从小到大的顺序保存了丑数。
var nthUglyNumber = function(n) {
const res = new Array(n);
res[0] = 1;
let ptr2 = 0, // 下个数字永远 * 2
ptr3 = 0, // 下个数字永远 * 3
ptr5 = 0; // 下个数字永远 * 5
for (let i = 1; i < n; ++i) {
res[i] = Math.min(res[ptr2] * 2, res[ptr3] * 3, res[ptr5] * 5);
if (res[i] === res[ptr2] * 2) {
++ptr2;
}
if (res[i] === res[ptr3] * 3) {
++ptr3;
}
if (res[i] === res[ptr5] * 5) {
++ptr5;
}
}
return res[n - 1];
};
分析一下这样的写法整体的复杂度,时间复杂度是O(N),空间复杂度是O(N)。看起来还不错的,都是线性的,感觉还能接受。
解法 2: 最小堆
思路:我们利用最小堆的思想,借助最小堆,可以在 O(LogN) 时间复杂度内找到当前最小的元素。如果我们遇到卡时间的数据,最小堆是最佳的选择。核心思路如下:
- 准备最小堆 heap。使用Set来去重,相当于来用于记录丑数是否Visited。
- 首先我们将 1 放入堆中,然后从 0 开始,遍历数组
- 取出堆顶元素,放入数组 res 中
- 用堆顶元素依此乘以 2、3、5,推入set去重
- 为了提高效率,我们使用的是hashset,其时间复杂度为O(1)
class Solution {
public int nthUglyNumber(int n) {
// 最小堆 + 集合去重,用的都是Long
PriorityQueue<Long> pq = new PriorityQueue<Long>();
HashSet<Long> set = new HashSet<Long>();
int[] ruler = {2, 3, 5};
pq.offer(1l);
long[] ans = new long[n];
for (int i = 0; i < n; i++) {
ans[i] = pq.poll();
for (int i1 : ruler) {
// 加入3个值(最小值 * 2, * 3 * 5)
if(!set.contains(ans[i] * i1) && ans[i] < Integer.MAX_VALUE){
pq.offer(ans[i] * i1);
set.add(ans[i] * i1);
}
}
}
return (int)ans[n - 1];
}
}
分析一下最小堆整体的复杂度,时间复杂度是O(logN), 空间复杂度是O(N)。在时间上做了优化。
感谢阅读,希望能对你有所帮助,文章若有错误或者侵权,可以在评论区留言或在我的主页添加公众号联系我。
写作不易,如果觉得不错,可以「点赞」+「评论」 谢谢支持❤