LeetCode題解:264. 醜數 II,二叉堆,JavaScript,詳細註釋
原題連線:https://leetcode-cn.com/problems/ugly-number-ii/
解題思路:
- 該題可使用堆解決,利用了堆能夠快速插入和取出元素,並始終能夠按要求排序的特點。
- 建立一個小頂堆,初始狀態下堆中儲存元素1,即為第一個醜數。第一次遍歷剛好可以計算出下一組醜數2、3、4。
- 因為堆中元素一直保持了從小到大排序,假設堆中已經儲存了所有醜數,那麼只需要從堆中取出n個數即可。
- 我們無需在每次執行時都計算出所有的醜數,再進行取出操作,每次只需要計算出足夠取出n個醜數的數量即可。
- 我們每次迴圈取出一個堆頂元素,將其分別乘以2、3、4,即可計算出一組醜數,但這一組醜數並不自然擁有從小到大的關係,因此只能插入堆中。
- 同時計算出的醜數可能是重複的,如23和32,因此需要使用Set標記當前的醜數是否已被儲存過。
- 迴圈計算並從堆中取出n次醜數,就得到了第n個醜數。
/**
* @param {number} n
* @return {number}
*/
var nthUglyNumber = function (n) {
let heap = new BinaryHeap((a, b) => a - b); // 建立一個小頂堆
let arr = [2, 3, 5]; // 將質因數儲存到陣列,方便進行遍歷計算
let set = new Set(); // 由於計算結果可能重複,如2*3和3*2,因此需要用Set標記醜數是否已被儲存過
let result = 1; // 1的質因數也可為2、3、4,因此設定初始結果為1
let count = 0; // 統計計算出的醜數個數
heap.insert(1); // 將1插入小頂堆,用於啟動遍歷
// 迴圈計算n個醜數
while (count < n) {
// 堆頂元素即為醜數,每次取出醜數都比上一次取出的大
// 由於堆的性質,取出之後依然會保持從小到大的排序
// 退出迴圈後,result儲存的就是第n個醜數
result = heap.deleteHead();
count++; // 每取出一個醜數,計數一次
// 分別將取出的醜數,乘以2、3、4,即為下一批的醜數
for (let i = 0; i < arr.length; i++) {
let product = result * arr[i]; // 計算出下一個醜數
// 如果計算出的醜數沒有被儲存過,則將其插入堆
if (!set.has(product)) {
// 醜數插入堆後,自然按照從小到大排序
heap.insert(product);
// 標記當前醜數已被插入堆中
set.add(product);
}
}
}
return result;
};
class BinaryHeap {
constructor(compare) {
this.data = []; // 使用陣列儲存堆
this.compare = compare; // 堆元素的排序函式
}
// 獲取堆的元素數量
size() {
return this.data.length;
}
// 向堆中插入多個元素
insertMultiple(arr) {
for (let i = 0; i < arr.length; i++) {
this.insert(arr[i]);
}
}
// 向堆插入元素
insert(value) {
this.insertAt(this.data.length, value);
}
// 將元素插入到index位置
insertAt(index, value) {
// 先將元素插入到指定的位置
this.data[index] = value;
let fatherIndex = index;
// 對比當前節點與其父節點,如果當前節點更小就交換它們
// Math.floor((index - 1) / 2)是父節點在陣列中的索引
while (
index > 0 &&
// 使用compare比較大小
this.compare(
value,
this.data[(fatherIndex = Math.floor((index - 1) / 2))],
) < 0
) {
// 將父節點移動到當前位置
this.data[index] = this.data[fatherIndex];
// 將插入的值移動到父節點位置
this.data[fatherIndex] = value;
// 更新索引為父節點索引,繼續下一次迴圈
index = fatherIndex;
}
}
// 刪除最大節點
deleteHead() {
return this.delete(0);
}
// 將指定位置的元素刪除
delete(index) {
// 如果堆為空,則不進行刪除操作
if (this.data.length === 0) {
return;
}
let value = this.data[index]; // 將要刪除的元素快取
let parent = index; // 以當前元素為起始,向下整理堆
// 不斷向子節點整理堆,每次迴圈將子節點中經過compare方法對比後較大者與父節點調換
while (parent < this.data.length) {
let left = parent * 2 + 1; // 左子節點索引
let right = parent * 2 + 2; // 右子節點索引
// 沒有左子節點,表示當前節點已經是最後一個節點
if (left >= this.data.length) {
break;
}
// 沒有右子節點,則直接將左子節點提前到父節點即可
// 該左子節點即為最後一個節點
if (right >= this.data.length) {
this.data[parent] = this.data[left];
parent = left;
break;
}
// 使用compare方法比較左右子節點的大小,更大的補到父節點
if (this.compare(this.data[left], this.data[right]) < 0) {
// 由於被刪除的節點已儲存,此處只需要將子節點複製到當前父節點即可
this.data[parent] = this.data[left];
// 完成移動後將父節點指標移動到子節點,供下一次整理使用
parent = left;
} else {
this.data[parent] = this.data[right];
parent = right;
}
}
// 檢視最後的空位是不是最後的葉子節點
if (parent < this.data.length - 1) {
// 如果還未整理到葉子節點,則繼續向下整理
this.insertAt(parent, this.data.pop());
} else {
// 當完成整理時,最後一個節點即為多於元素,直接彈出陣列即可
this.data.pop();
}
// 返回被刪除的元素
return value;
}
// 刪除指定元素
deleteItem(value) {
// 查詢元素在堆中對應的索引
const index = this.data.findIndex((item) => item === value);
// 根據索引刪除相應元素
if (typeof index === 'number') {
this.delete(index);
}
}
// 刪除指定元素
deleteItem(value) {
// 查詢元素在堆中對應的索引
const index = this.data.findIndex((item) => item === value);
// 根據索引刪除相應元素
if (typeof index === 'number') {
this.delete(index);
}
}
// 讀取堆頂元素
peek() {
return this.data[0];
}
// 讀取所有堆元素
getData() {
return this.data;
}
}
相關文章
- LeetCode題解:劍指 Offer 40. 最小的k個數,二叉堆,JavaScript,詳細註釋LeetCodeJavaScript
- LeetCode題解:127. 單詞接龍,雙向BFS,JavaScript,詳細註釋LeetCodeJavaScript
- LeetCode (39) Ugly Number I II (醜數)LeetCode
- LeetCode題解:641. 設計迴圈雙端佇列,使用佇列,JavaScript,詳細註釋LeetCode佇列JavaScript
- JavaScript註釋:單行註釋和多行註釋詳解JavaScript
- Leetcode:2. 兩數相加(C++帶詳細註釋)LeetCodeC++
- Leetcode 167:兩數之和 II - 輸入有序陣列(最詳細解決方案!!!)LeetCode陣列
- 264、醜數 II | 演算法(leetcode,附思維導圖 + 全部解法)300題演算法LeetCode
- MySQL relay log 詳細引數解釋MySql
- mysqldump匯出引數詳細解釋MySql
- TERMIOS_H 詳細註釋iOS
- sqlHelper類的中文 詳細註釋SQL
- Leetcode 253:Meeting Rooms II(超詳細的解法!!!)LeetCodeOOM
- UDEV規則引數詳細解釋使用dev
- [LeetCode] Ugly Number 醜陋數LeetCode
- Java註解最全詳解(超級詳細)Java
- Tarjan演算法及其應用 總結+詳細講解+詳細程式碼註釋演算法
- 生動詳細解釋javascript的冒泡和捕獲JavaScript
- 【leetcode】每日精選題詳解之59. 螺旋矩陣 IILeetCode矩陣
- Leetcode 137:只出現一次的數字 II(最詳細的解法!!!)LeetCode
- docker 命令詳細解釋Docker
- top命令詳細解釋
- Servlet、HTTP詳細解釋!ServletHTTP
- SpringBoot註解大全(詳細)Spring Boot
- 「轉」Laravel 依賴注入原理(詳細註釋)Laravel依賴注入
- Bootstrap的Model原始碼詳細註釋 (轉)boot原始碼
- 關於quartus ii軟體中註釋亂碼問題的解決方法
- 引數session_cached_cursors的詳細解釋(zt)Session
- MongoDB啟動命令mongod引數的詳細解釋MongoDB
- 最詳細版圖解優先佇列(堆)圖解佇列
- LeetCode|劍指 Offer 49.醜數LeetCode
- EventBus原始碼解讀詳細註釋(1)register的幕後黑手原始碼
- IE條件註釋詳解
- LeetCode刷題 堆LeetCode
- JavaScript 註釋JavaScript
- [譯] 程式碼中新增註釋之好壞醜
- [獻醜了!] Android AOP註解GoodAtAndroidGo
- JPS 命令詳細解釋