Successful Pairs of Spells and Potions
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.lengthm == potions.length1 <= n, m <= 10^51 <= spells[i], potions[i] <= 10^51 <= 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
- For each potion, compute the minimum spell strength needed and count occurrences
- Build a prefix sum so that
sums[s]= count of potions requiring spell strength<= s - 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 exactlyisums[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
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Binary Search | O(m log m + n log m) | O(m) for sorting |
| Prefix Sum | O(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