Successful Pairs of Spells and Potions

題目描述

給定 spellspotions 兩個陣列,以及一個整數 success。當 spells[i] * potions[j] >= success 時為成功配對。回傳一個陣列,其中第 i 個元素是第 i 個法術的成功配對數。

輸入:spells = [5,1,3], potions = [1,2,3,4,5], success = 7
輸出:[4,0,3]
解釋:spell=5 配對 [2,3,4,5]→4個, spell=1 配對 0個, spell=3 配對 [3,4,5]→3個

解題思路

1

排序藥水

將 potions 升序排列,排序後相同強度的藥水聚在一起,便於二分搜尋

2

計算門檻

對每個 spell,最低需要的藥水強度 = ceil(success / spell)。例如 success=7, spell=3 → 門檻 = ceil(7/3) = 3

3

二分搜尋

在排序後的 potions 中用 bisect_left 找第一個 ≥ 門檻的位置 pos

4

計算配對數

pos 及其右邊的藥水都能成功配對,數量 = potions.length - pos

程式碼

C#

csharp
public class Solution {
    public int[] SuccessfulPairs(int[] spells, int[] potions, long success) {
        Array.Sort(potions);
        int n = potions.Length;
        int[] result = new int[spells.Length];
        
        for (int i = 0; i < spells.Length; i++) {
            // 最低需要的藥水強度
            long minPotion = (success + spells[i] - 1) / spells[i]; // ceil division
            // 二分搜尋第一個 >= minPotion 的位置
            int left = 0, right = n;
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (potions[mid] >= minPotion) right = mid;
                else left = mid + 1;
            }
            result[i] = n - left;
        }
        return result;
    }
}

TypeScript

typescript
function successfulPairs(spells: number[], potions: number[], success: number): number[] {
    potions.sort((a, b) => a - b);
    const n = potions.length;
    
    return spells.map(spell => {
        const minPotion = Math.ceil(success / spell);
        // 二分搜尋第一個 >= minPotion 的位置
        let left = 0, right = n;
        while (left < right) {
            const mid = Math.floor((left + right) / 2);
            if (potions[mid] >= minPotion) right = mid;
            else left = mid + 1;
        }
        return n - left;
    });
}

Python

python
import bisect
import math
 
def successfulPairs(spells: list[int], potions: list[int], success: int) -> list[int]:
    potions.sort()
    n = len(potions)
    result = []
    
    for spell in spells:
        min_potion = math.ceil(success / spell)
        # bisect_left 找第一個 >= min_potion 的位置
        idx = bisect.bisect_left(potions, min_potion)
        result.append(n - idx)
    
    return result

複雜度分析

| | Time | Space | |--|------|-------| | 本解法 | O((m + n) log n) | O(1)(不計輸出) |

其中 m = spells 長度,n = potions 長度。

圖解

排序 potions
排序後
遍歷每個 spell
ceil(success/spell)
計算門檻值
在 potions 中找
Binary Search 找位置
計算數量
n - pos = 成功配對數

流程展示「排序 + 二分搜尋」的經典模式:一次排序 O(n log n),然後對每個 spell 用 O(log n) 找到分界點。 分界點右邊的所有藥水乘以 spell 都 ≥ success,直接用 n - pos 算出配對數。

進階觀點

💡面試追問

  • 為什麼要用 ceil(success / spell) 而不是直接除? 因為需要 spell × potion ≥ success,換算成 potion ≥ success / spell。整數除法會截斷小數(如 7/3=2),但 potion=2 時 3×2=6 不夠 7,所以必須向上取整到 3。整數 ceil 公式:(success + spell - 1) / spell
  • 如果 spells 也排序,能用雙指標優化嗎? 可以。將 spells 降序排序後,spell 越小門檻越高,二分搜尋的起始位置只會右移。但需要額外的索引映射來還原原始順序,增加實作複雜度。
  • 這題的核心模式是什麼? 「排序 + 二分搜尋」——當需要對每個查詢計算「滿足條件的元素數量」時,先排序再二分是最典型的做法。

邊界情況

理解測驗

🤔 為什麼先排序 potions 再用二分搜尋,而不是對每個 spell 暴力遍歷 potions?

🤔 如果某個 spell 非常大,使得所有 potions 都能成功配對,二分搜尋會返回什麼位置?

你可能也想看

374. Guess Number Higher or Lower162. Find Peak Element

按 ← → 鍵切換課程