2300. Successful Pairs of Spells and Potions
Problem Description
You have two arrays of positive integers: spells and potions. The spells array has length n and represents the strength of each spell. The potions array has length m and represents the strength of each potion.
You need to determine how many potions can form a successful pair with each spell. A spell and potion form a successful pair when their product (spell strength × potion strength) is at least equal to a given integer value called success.
For each spell in the spells array, you need to count how many potions from the potions array can be paired with it successfully. Return an array pairs of length n, where pairs[i] represents the number of potions that form a successful pair with the i-th spell.
For example, if a spell has strength 5 and success is 20, then any potion with strength 4 or greater would form a successful pair (since 5 × 4 = 20, which meets the threshold).
The solution approach sorts the potions array first, then for each spell v, uses binary search to find the smallest potion that satisfies potion × v ≥ success, which is equivalent to finding potion ≥ success/v. The count of valid potions is the total number of potions minus the index where this threshold is met.
Intuition
The key insight is that for a given spell with strength v, we need to find all potions with strength p such that v × p ≥ success. This can be rearranged to p ≥ success/v.
Once we recognize this inequality, we realize that if we sort the potions array, all valid potions will form a contiguous segment at the end of the sorted array. Why? Because if a potion with strength p works with a spell, then any potion with strength greater than p will also work.
This observation leads us to binary search. For each spell, we don't need to check every potion individually. Instead, we can:
- Sort the potions array once
- For each spell, find the minimum potion strength needed:
success/v - Use binary search to locate where this threshold value would fit in the sorted potions array
- All potions from this position to the end of the array are valid pairs
The beauty of this approach is that sorting costs O(m log m) once, and then each spell only requires O(log m) time for binary search, giving us a total time complexity of O(m log m + n log m). This is much more efficient than the naive approach of checking every spell-potion pair, which would take O(n × m) time.
The bisect_left function finds the leftmost position where we could insert success/v to keep the array sorted. This gives us the index of the first potion that meets our requirement. Subtracting this index from the total length gives us the count of valid potions.
Learn more about Two Pointers, Binary Search and Sorting patterns.
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4
5class Solution:
6 def successfulPairs(
7 self, spells: List[int], potions: List[int], success: int
8 ) -> List[int]:
9 """
10 Find the number of successful pairs for each spell.
11 A pair (spell, potion) is successful if spell * potion >= success.
12
13 Args:
14 spells: List of spell strengths
15 potions: List of potion strengths
16 success: Minimum product value for a successful pair
17
18 Returns:
19 List where each element represents the count of successful pairs for each spell
20 """
21 # Sort potions array to enable binary search
22 potions.sort()
23
24 # Store the total number of potions
25 num_potions = len(potions)
26
27 # For each spell, find how many potions create a successful pair
28 result = []
29 for spell_strength in spells:
30 # Calculate minimum potion strength needed for this spell
31 # We need: spell * potion >= success, so potion >= success / spell
32 min_potion_strength = success / spell_strength
33
34 # Use binary search to find the leftmost position where
35 # potion strength >= min_potion_strength
36 insert_position = bisect_left(potions, min_potion_strength)
37
38 # All potions from insert_position to end are successful pairs
39 successful_count = num_potions - insert_position
40 result.append(successful_count)
41
42 return result
431class Solution {
2 public int[] successfulPairs(int[] spells, int[] potions, long success) {
3 // Sort potions array to enable binary search
4 Arrays.sort(potions);
5
6 // Get array lengths
7 int spellsLength = spells.length;
8 int potionsLength = potions.length;
9
10 // Initialize result array to store count of successful pairs for each spell
11 int[] result = new int[spellsLength];
12
13 // Iterate through each spell
14 for (int i = 0; i < spellsLength; i++) {
15 // Binary search using first_true_index template
16 // Find first potion where spell * potion >= success
17 int left = 0;
18 int right = potionsLength - 1;
19 int firstTrueIndex = potionsLength; // Default: no valid potion found
20
21 while (left <= right) {
22 int mid = left + (right - left) / 2;
23
24 // Check if current spell multiplied by middle potion meets success threshold
25 if ((long) spells[i] * potions[mid] >= success) {
26 // Found a valid potion, record and search for earlier one
27 firstTrueIndex = mid;
28 right = mid - 1;
29 } else {
30 // Not successful, search in right half
31 left = mid + 1;
32 }
33 }
34
35 // Calculate number of successful pairs
36 // All potions from firstTrueIndex to end form successful pairs
37 result[i] = potionsLength - firstTrueIndex;
38 }
39
40 return result;
41 }
42}
431class Solution {
2public:
3 vector<int> successfulPairs(vector<int>& spells, vector<int>& potions, long long success) {
4 // Sort potions array in ascending order for binary search
5 sort(potions.begin(), potions.end());
6
7 // Result vector to store count of successful pairs for each spell
8 vector<int> result;
9
10 // Get the total number of potions
11 int potionCount = potions.size();
12
13 // Iterate through each spell
14 for (int& spellStrength : spells) {
15 // Binary search using first_true_index template
16 // Find first potion where spell * potion >= success
17 int left = 0;
18 int right = potionCount - 1;
19 int firstTrueIndex = potionCount; // Default: no valid potion found
20
21 while (left <= right) {
22 int mid = left + (right - left) / 2;
23
24 // Check if current spell * potion meets success threshold
25 if ((long long) spellStrength * potions[mid] >= success) {
26 // Found a valid potion, record and search for earlier one
27 firstTrueIndex = mid;
28 right = mid - 1;
29 } else {
30 // Not successful, search in right half
31 left = mid + 1;
32 }
33 }
34
35 // Calculate number of successful pairs
36 // All potions from firstTrueIndex to end form successful pairs
37 result.push_back(potionCount - firstTrueIndex);
38 }
39
40 return result;
41 }
42};
431/**
2 * Counts the number of successful pairs for each spell with potions
3 * A pair is successful if spell * potion >= success threshold
4 *
5 * @param spells - Array of spell strengths
6 * @param potions - Array of potion strengths
7 * @param success - Minimum product value for a successful pair
8 * @returns Array where each element represents the count of successful pairs for each spell
9 */
10function successfulPairs(spells: number[], potions: number[], success: number): number[] {
11 // Sort potions in ascending order for binary search
12 potions.sort((a: number, b: number) => a - b);
13
14 const potionCount: number = potions.length;
15 const result: number[] = [];
16
17 // Process each spell
18 for (const spellStrength of spells) {
19 // Binary search using first_true_index template
20 // Find first potion where spell * potion >= success
21 let left: number = 0;
22 let right: number = potionCount - 1;
23 let firstTrueIndex: number = potionCount; // Default: no valid potion found
24
25 while (left <= right) {
26 const mid: number = Math.floor((left + right) / 2);
27
28 // Check if current potion creates a successful pair with the spell
29 if (spellStrength * potions[mid] >= success) {
30 // Found a valid potion, record and search for earlier one
31 firstTrueIndex = mid;
32 right = mid - 1;
33 } else {
34 // Not successful, search in right half
35 left = mid + 1;
36 }
37 }
38
39 // Count of successful pairs = total potions - firstTrueIndex
40 result.push(potionCount - firstTrueIndex);
41 }
42
43 return result;
44}
45Solution Approach
The implementation follows a sorting and binary search strategy.
Step 1: Sort the potions array
potions.sort()
We sort the potions array in ascending order. This preprocessing step enables us to use binary search efficiently.
Step 2: Binary Search Using first_true_index Template
For each spell with strength v, we need to find the first potion that forms a successful pair (where potion >= success / v).
Using the first_true_index template:
def bisect_left(nums, target):
# Find first index where nums[idx] >= target
left, right = 0, len(nums) - 1
first_true_index = len(nums) # Default: no element >= target
while left <= right:
mid = (left + right) // 2
if nums[mid] >= target:
first_true_index = mid
right = mid - 1
else:
left = mid + 1
return first_true_index
Counting Logic:
first_true_index = bisect_left(potions, success / v)finds the first potion >= threshold- Number of valid potions =
len(potions) - first_true_index
Example walkthrough:
If potions = [1, 2, 3, 4, 5], success = 7, and current spell strength is 2:
- Minimum potion needed:
7 / 2 = 3.5 bisect_left([1, 2, 3, 4, 5], 3.5)returns3(first index where value >= 3.5)- Valid potions are at indices 3 and 4 (values 4 and 5)
- Count of valid potions:
5 - 3 = 2
Time Complexity: O((m + n) × log m) - sort potions + binary search per spell.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand the solution approach:
Given:
spells = [3, 1, 2]potions = [8, 5, 8]success = 16
Step 1: Sort the potions array
potions = [5, 8, 8](sorted in ascending order)m = 3(length of potions)
Step 2: Process each spell using binary search
For spell[0] = 3:
- We need:
3 × potion ≥ 16 - Minimum potion strength needed:
16 / 3 = 5.33... - Use
bisect_left([5, 8, 8], 5.33)to find where 5.33 would be inserted - Binary search finds index
1(5.33 would go after the 5 at index 0) - Valid potions are at indices 1 and 2 (values 8 and 8)
- Count:
3 - 1 = 2successful pairs
For spell[1] = 1:
- We need:
1 × potion ≥ 16 - Minimum potion strength needed:
16 / 1 = 16 - Use
bisect_left([5, 8, 8], 16)to find where 16 would be inserted - Binary search finds index
3(16 would go at the end) - No potions meet this threshold
- Count:
3 - 3 = 0successful pairs
For spell[2] = 2:
- We need:
2 × potion ≥ 16 - Minimum potion strength needed:
16 / 2 = 8 - Use
bisect_left([5, 8, 8], 8)to find where 8 would be inserted - Binary search finds index
1(the leftmost position of value 8) - Valid potions are at indices 1 and 2 (values 8 and 8)
- Count:
3 - 1 = 2successful pairs
Final Result: [2, 0, 2]
This example demonstrates how sorting the potions array once allows us to efficiently find the count of valid pairs for each spell using binary search, avoiding the need to check every spell-potion combination individually.
Time and Space Complexity
Time Complexity: O(m log m + n log m) which simplifies to O((m + n) × log m)
- Sorting the potions array takes
O(m log m)time, wheremis the length of the potions array - For each spell in the spells array, we perform a binary search using
bisect_lefton the sorted potions array, which takesO(log m)time per spell - Since we have
nspells, the total time for all binary searches isO(n log m) - Overall time complexity:
O(m log m + n log m) = O((m + n) × log m)
Space Complexity: O(log m)
- The sorting operation on the potions array uses
O(log m)space for the recursion stack in Python's Timsort algorithm - The list comprehension creates a new list of size
n, but this is the output and typically not counted in space complexity analysis - The binary search operation
bisect_leftusesO(1)additional space - Overall auxiliary space complexity:
O(log m)
Note: The reference answer states O(log n) for space complexity, but based on the code analysis, it should be O(log m) since the sorting is performed on the potions array of length m, not the spells array of length n.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template
The problem uses bisect_left which finds the first index where element >= target.
Wrong approach:
while left < right: mid = (left + right) >> 1 if spell * potions[mid] >= success: right = mid else: left = mid + 1 return left # Returns left directly
Correct approach (first_true_index template):
first_true_index = len(potions) # Default
while left <= right:
mid = (left + right) // 2
if spell * potions[mid] >= success:
first_true_index = mid
right = mid - 1
else:
left = mid + 1
return first_true_index
2. Integer Division vs Float Division
The Pitfall: Using integer division (//) instead of float division (/) when calculating the minimum potion strength.
# WRONG - This will truncate the result min_potion_strength = success // spell_strength # CORRECT - Preserves decimal values min_potion_strength = success / spell_strength
Why it matters: If success = 7 and spell_strength = 2, we need potions with strength ≥ 3.5. Using integer division would give us 3, incorrectly including potions with strength 3 (since 2 × 3 = 6 < 7).
3. Using bisect_right Instead of bisect_left
The Pitfall: Confusing bisect_right with bisect_left, which would give incorrect counts when exact matches exist.
# WRONG - May miss valid potions when exact threshold exists insert_position = bisect_right(potions, min_potion_strength) # CORRECT - Finds the leftmost valid position insert_position = bisect_left(potions, min_potion_strength)
Example: If potions = [1, 2, 4, 4, 5] and we need min_potion_strength = 4:
bisect_leftreturns index 2 (first occurrence of 4)bisect_rightreturns index 4 (after last occurrence of 4)- Using
bisect_rightwould incorrectly exclude the two potions with strength 4
4. Forgetting to Sort the Potions Array
The Pitfall: Applying binary search on an unsorted array, leading to incorrect results.
# WRONG - Binary search requires sorted array result = [m - bisect_left(potions, success / v) for v in spells] # CORRECT - Sort first, then apply binary search potions.sort() result = [m - bisect_left(potions, success / v) for v in spells]
5. Modifying Original Potions Array
The Pitfall: Sorting the original potions array in-place might be problematic if the original order needs to be preserved elsewhere.
Solution: Create a copy if needed:
sorted_potions = sorted(potions) # Creates a new sorted list
# OR
sorted_potions = potions.copy()
sorted_potions.sort()
6. Edge Case: Very Small Spell Strength
The Pitfall: When spell strength approaches zero, success / spell_strength can become very large or cause floating-point precision issues.
Solution: While the problem states all values are positive integers, it's good practice to handle edge cases:
for spell_strength in spells: if spell_strength == 0: # Though problem guarantees positive integers result.append(0) continue min_potion_strength = success / spell_strength # ... rest of the logic
Which of the following is a good use case for backtracking?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!