|
| 1 | +# 2141. Maximum Running Time of N Computers |
| 2 | + |
| 3 | +You have `n` computers. |
| 4 | +You are given the integer `n` and a 0-indexed integer array `batteries` where the $i^{th}$ battery can run a computer for` batteries[i]` minutes. |
| 5 | +You are interested in running all `n` computers simultaneously using the given `batteries`. |
| 6 | + |
| 7 | +Initially, you can insert at most one battery into each computer. |
| 8 | +After that and at any integer time moment, you can remove a battery from a computer and insert another battery any number of times. |
| 9 | +The inserted battery can be a totally new battery or a battery from another computer. |
| 10 | +You may assume that the removing and inserting processes take no time. |
| 11 | + |
| 12 | +Note that the batteries cannot be recharged. |
| 13 | + |
| 14 | +Return the maximum number of minutes you can run all the `n` computers simultaneously. |
| 15 | + |
| 16 | +**Constraints:** |
| 17 | + |
| 18 | +- `1 <= n <= batteries.length <= 10^5` |
| 19 | +- `1 <= batteries[i] <= 10^9` |
| 20 | + |
| 21 | +## 基礎思路 |
| 22 | + |
| 23 | +本題要求同時運行 `n` 台電腦,並允許在任意整數時間點自由交換電池,但電池不可充電。每個電池的容量固定,因此所有電池的總可用時間是固定資源。我們的目標是找出所有電腦能同時運行的最長分鐘數。 |
| 24 | + |
| 25 | +可掌握以下幾項核心觀察: |
| 26 | + |
| 27 | +* **交換電池不耗時,因此可視為所有電池時間可自由分配。** |
| 28 | + 每個電池都能將自身的時間拆分成任意段並分配給不同電腦,只要總貢獻不超出其容量。 |
| 29 | + |
| 30 | +* **所有電腦需同時運作,代表每台電腦必須得到相同的最終可用時間。** |
| 31 | + 若每台需運作 `T` 分鐘,總需求必須至少為 `n × T`。 |
| 32 | + |
| 33 | +* **每顆電池能貢獻的時間上限為 `min(battery[i], T)`。** |
| 34 | + 因為電池不可超時使用,因此電池對目標時間 `T` 的最大貢獻是其容量或 T 中的較小者。 |
| 35 | + |
| 36 | +* **可行性判斷可透過檢查所有電池的總貢獻是否 ≥ `n × T`。** |
| 37 | + 若總貢獻足夠,代表可以讓所有電腦同時運行 T 分鐘。 |
| 38 | + |
| 39 | +* **答案具有單調性,因此可使用二分搜尋。** |
| 40 | + 若某個時間 `T` 可以達成,則所有比 T 更小的時間也一定可以達成;反之亦然。 |
| 41 | + |
| 42 | +基於上述特性,可採用: |
| 43 | + |
| 44 | +* 先計算所有電池總時數的上限作為右界。 |
| 45 | +* 使用二分搜尋找出最大可行運作時間。 |
| 46 | +* 用可行性檢查函式來判定某個分鐘數是否可讓所有電腦同時持續運行。 |
| 47 | + |
| 48 | +此策略能在大規模輸入下依然維持高效運算。 |
| 49 | + |
| 50 | +## 解題步驟 |
| 51 | + |
| 52 | +### Step 1:初始化電腦數量、電池數量並建立 typed array |
| 53 | + |
| 54 | +先計算電腦與電池數量,並建立 typed array 以取得更緊密的記憶體佈局與更快的隨機存取效率。 |
| 55 | + |
| 56 | +```typescript |
| 57 | +const computerCount = n; |
| 58 | +const batteryCount = batteries.length; |
| 59 | + |
| 60 | +// 將電池內容放入 typed array 以獲得更佳記憶體配置 |
| 61 | +const batteryTimes = new Float64Array(batteryCount); |
| 62 | +``` |
| 63 | + |
| 64 | +### Step 2:複製電池資料並累加全部電池總時數 |
| 65 | + |
| 66 | +在複製過程中順便計算所有電池容量之總和,以便後續推算二分搜尋的右界。 |
| 67 | + |
| 68 | +```typescript |
| 69 | +// 複製電池內容同時計算總電池時數 |
| 70 | +let totalBatteryTime = 0; |
| 71 | +for (let index = 0; index < batteryCount; index++) { |
| 72 | + const batteryTime = batteries[index]; |
| 73 | + batteryTimes[index] = batteryTime; |
| 74 | + totalBatteryTime += batteryTime; |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +### Step 3:處理僅有一台電腦的特例 |
| 79 | + |
| 80 | +若只有一台電腦,因為交換無成本,可直接將所有電池時間全部使用,因此答案即為總電池時間。 |
| 81 | + |
| 82 | +```typescript |
| 83 | +// 特例:若只有一台電腦,可直接使用全部電池時數 |
| 84 | +if (computerCount === 1) { |
| 85 | + return totalBatteryTime; |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +### Step 4:設定二分搜尋的左右界 |
| 90 | + |
| 91 | +左界代表最低可行時間(0),右界為所有電池總時間平均分給每台電腦的最大可能值。 |
| 92 | + |
| 93 | +```typescript |
| 94 | +// 在最理想分配情況下的最大可能時間 |
| 95 | +let leftTime = 0; |
| 96 | +let rightTime = Math.floor(totalBatteryTime / computerCount); |
| 97 | +``` |
| 98 | + |
| 99 | +### Step 5:定義可行性檢查函式 |
| 100 | + |
| 101 | +此函式用於判定是否能讓所有電腦同時運行指定分鐘數: |
| 102 | +每顆電池貢獻 `min(batteryTime, targetMinutes)`,總貢獻需至少達到 `n × targetMinutes`。 |
| 103 | + |
| 104 | +```typescript |
| 105 | +/** |
| 106 | + * 檢查是否能讓所有電腦同時運行 targetMinutes 分鐘 |
| 107 | + * |
| 108 | + * @param {number} targetMinutes - 目標運行時間 |
| 109 | + * @return {boolean} - 是否可行 |
| 110 | + */ |
| 111 | +function canPowerAllComputers(targetMinutes: number): boolean { |
| 112 | + const requiredTotalTime = targetMinutes * computerCount; |
| 113 | + let accumulatedTime = 0; |
| 114 | + |
| 115 | + // 若目標為 0 則永遠可行 |
| 116 | + if (targetMinutes === 0) { |
| 117 | + return true; |
| 118 | + } |
| 119 | + |
| 120 | + // 累計所有電池可貢獻的時間,並在足夠時提前返回 |
| 121 | + for (let index = 0; index < batteryCount; index++) { |
| 122 | + const batteryTime = batteryTimes[index]; |
| 123 | + |
| 124 | + // 單顆電池最多貢獻 targetMinutes |
| 125 | + if (batteryTime >= targetMinutes) { |
| 126 | + accumulatedTime += targetMinutes; |
| 127 | + } else { |
| 128 | + accumulatedTime += batteryTime; |
| 129 | + } |
| 130 | + |
| 131 | + // 若已累積足夠時間則可行 |
| 132 | + if (accumulatedTime >= requiredTotalTime) { |
| 133 | + return true; |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + return false; |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +### Step 6:使用二分搜尋找出最大可行運行時間 |
| 142 | + |
| 143 | +透過二分搜尋逐步縮小可行時間的範圍,使用上中位數避免死循環,並按可行性結果調整左右界。 |
| 144 | + |
| 145 | +```typescript |
| 146 | +// 二分搜尋最大可行運行時間 |
| 147 | +while (leftTime < rightTime) { |
| 148 | + // 使用上中位數以避免陷入無窮迴圈 |
| 149 | + const middleTime = Math.floor((leftTime + rightTime + 1) / 2); |
| 150 | + |
| 151 | + if (canPowerAllComputers(middleTime)) { |
| 152 | + // 若可行,嘗試更長的時間 |
| 153 | + leftTime = middleTime; |
| 154 | + } else { |
| 155 | + // 若不可行,壓低右界 |
| 156 | + rightTime = middleTime - 1; |
| 157 | + } |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +### Step 7:回傳最大同時運行時間 |
| 162 | + |
| 163 | +當二分搜尋結束後,左界即為最大可行時間。 |
| 164 | + |
| 165 | +```typescript |
| 166 | +return leftTime; |
| 167 | +``` |
| 168 | + |
| 169 | +## 時間複雜度 |
| 170 | + |
| 171 | +- 建立 typed array 與複製電池資料需要處理 `batteries.length` 次。 |
| 172 | +- 可行性檢查每次需遍歷全部電池,共會在二分搜尋中執行 `O(log(totalBatteryTime))` 次。 |
| 173 | +- 總時間複雜度為 $O(m \log M)$,其中 |
| 174 | + `m = batteries.length`, |
| 175 | + `M = totalBatteryTime / n`。 |
| 176 | + |
| 177 | +> $O(m \log M)$ |
| 178 | +
|
| 179 | +## 空間複雜度 |
| 180 | + |
| 181 | +- 使用一個長度為 `m` 的 typed array 存放電池資料。 |
| 182 | +- 其餘僅使用固定數量變數。 |
| 183 | +- 總空間複雜度為 $O(m)$。 |
| 184 | + |
| 185 | +> $O(m)$ |
0 commit comments