Successful Pairs of Spells and Potions

array two-pointers binary-search sorting prefix-sum

Description

You are given two positive integer arrays spells and potions, of length n and m respectively, where spells[i] represents the strength of the ith spell and potions[j] represents the strength of the jth potion.

You are also given an integer success. A spell and potion pair is considered successful if the product of their strengths is at least success.

Return an integer array pairs of length n where pairs[i] is the number of potions that will form a successful pair with the ith spell.

Example 1:
Input: spells = [5,1,3], potions = [1,2,3,4,5], success = 7
Output: [4,0,3]
Explanation:
- 0th spell: 5 [1,2,3,4,5] = [5,10,15,20,25]. 4 pairs are successful (2,3,4,5).
- 1st spell: 1
[1,2,3,4,5] = [1,2,3,4,5]. 0 pairs are successful (all < 7).
- 2nd spell: 3 * [1,2,3,4,5] = [3,6,9,12,15]. 3 pairs are successful (3,4,5).

Example 2:
Input: spells = [3,1,2], potions = [8,5,8], success = 16
Output: [2,0,2]
Explanation:
- 0th spell: 3 [8,5,8] = [24,15,24]. 2 pairs are successful (8,8).
- 1st spell: 1
[8,5,8] = [8,5,8]. 0 pairs are successful (all < 16).
- 2nd spell: 2 * [8,5,8] = [16,10,16]. 2 pairs are successful (8,8).

Constraints

  • n == spells.length
  • m == potions.length
  • 1 <= n, m <= 10^5
  • 1 <= spells[i], potions[i] <= 10^5
  • 1 <= success <= 10^10

Complexity

Show Complexity
  • Time: O((n + m) * log m)
  • Space: O(m)

Hints

Show Hints
Pattern
Binary search after sorting
Approach
For spell s, need potion p where s*p >= success, so p >= ceil(success/s). Binary search for first potion >= this threshold, count remaining potions.
Complexity
Sort potions, then for each spell binary search for minimum potion strength needed

Notes

Show Notes

Successful Pairs of Spells and Potions

Problem

Given arrays spells and potions, and an integer success, count how many potions form a successful pair with each spell. A pair is successful if spell * potion >= success.

Approach 1: Binary Search

Sort potions, then for each spell binary search for the minimum potion strength needed.

def solution(spells: list[int], potions: list[int], success: int) -> list[int]:
    potions = sorted(potions)
    n = len(potions)

    results = []
    for s in spells:
        left = 0
        right = len(potions)
        while left < right:
            mid = left + (right - left) // 2
            if potions[mid] >= success / s:
                right = mid
            else:
                left = mid + 1
        results.append(n - left)

    return results

Time Complexity: O(m log m + n log m) where m = len(potions), n = len(spells)

Approach 2: Prefix Sum (Counting)

Instead of sorting and searching, use a counting approach based on the observation that for each potion, we can precompute the minimum spell strength required.

Key Insight

For a potion p to form a successful pair with a spell s:

s * p >= success
s >= success / p
s >= ceil(success / p)

So ceil(success / p) is the minimum spell strength needed to succeed with potion p.

Algorithm

  1. For each potion, compute the minimum spell strength needed and count occurrences
  2. Build a prefix sum so that sums[s] = count of potions requiring spell strength <= s
  3. Look up the answer for each spell directly
def solution(spells: list[int], potions: list[int], success: int) -> list[int]:
    max_spell = max(spells)
    sums = [0] * (max_spell + 1)

    for potion in potions:
        curr = math.ceil(success / potion)
        if curr > max_spell:
            continue
        sums[curr] += 1

    for i in range(1, len(sums)):
        sums[i] = sums[i] + sums[i - 1]

    return [sums[s] for s in spells]

Why Prefix Sum Works

After building sums:

  • sums[i] (before prefix sum) = count of potions where minimum required spell is exactly i
  • sums[i] (after prefix sum) = count of potions where minimum required spell is <= i

A spell of strength s succeeds with any potion that requires <= s. Therefore sums[s] gives the exact count of successful potions.

Example Trace

spells=[5,1,3], potions=[1,2,3,4,5], success=7

Step 1: For each potion, compute minimum spell needed
- potion=1: ceil(7/1) = 7 > max_spell(5), skip
- potion=2: ceil(7/2) = 4 → sums[4] += 1
- potion=3: ceil(7/3) = 3 → sums[3] += 1
- potion=4: ceil(7/4) = 2 → sums[2] += 1
- potion=5: ceil(7/5) = 2 → sums[2] += 1

sums = [0, 0, 2, 1, 1, 0]  (indices 0-5)

Step 2: Build prefix sum
sums = [0, 0, 2, 3, 4, 4]

Step 3: Look up results
- spell=5: sums[5] = 4
- spell=1: sums[1] = 0
- spell=3: sums[3] = 3

Output: [4, 0, 3]

Time Complexity: O(m + max_spell + n)

Comparison

ApproachTime ComplexitySpace Complexity
Binary SearchO(m log m + n log m)O(m) for sorting
Prefix SumO(m + max_spell + n)O(max_spell)

When max_spell is bounded (constraint: <= 10^5), prefix sum is effectively O(m + n), avoiding the log factor entirely.

The prefix sum approach trades space (array of size max_spell) for time (no log factors). This is a counting/bucket sort style optimization that works when the value range is bounded.

Solutions

Show PY Solution
import math


def solution(spells: list[int], potions: list[int], success: int) -> list[int]:
    # success if spell * potion >= success

    max_spell = max(spells)
    sums = [0] * (max_spell + 1)
    for potion in potions:
        curr = math.ceil(success / potion)
        if curr > max_spell:
            continue
        sums[curr] += 1

    for i in range(1, len(sums)):
        sums[i] = sums[i] + sums[i - 1]

    # print(sums)
    return [sums[s] for s in spells]

    # Binary search
    # potions = sorted(potions)
    # n = len(potions)
    #
    # results = []
    # for s in spells:
    #     left = 0
    #     right = len(potions)
    #     while left < right:
    #         mid = left + (right - left) // 2
    #
    #         if potions[mid] >= success / s:
    #             right = mid
    #         else:
    #             left = mid + 1
    #
    #     results.append(n - left)
    #
    # return results