Successful Pairs of Spells and Potions
MedPrompt
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.
- 1st spell: 1 * [1,2,3,4,5] = [1,2,3,4,5]. 0 pairs are successful.
- 2nd spell: 3 * [1,2,3,4,5] = [3,6,9,12,15]. 3 pairs are successful.
Thus, [4,0,3] is returned.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.
- 1st spell: 1 * [8,5,8] = [8,5,8]. 0 pairs are successful.
- 2nd spell: 2 * [8,5,8] = [16,10,16]. 2 pairs are successful.
Thus, [2,0,2] is returned.
Constraints:
n == spells.lengthm == potions.length1 <= n, m <= 1051 <= spells[i], potions[i] <= 1051 <= success <= 1010
Approaches
3 approaches with complexity analysis and trade-offs.
This is a highly optimized approach that involves sorting both the spells and potions arrays and then using a two-pointer technique to find the counts efficiently. By processing spells in increasing order of strength, we can avoid re-scanning the potions array for each spell.
Algorithm
-
- Create a 2D array
sortedSpellsof sizen x 2to store(spell, original_index).
- Create a 2D array
-
- Sort
sortedSpellsbased on spell strength.
- Sort
-
- Sort the
potionsarray.
- Sort the
-
- Initialize
potionIndex = m - 1and the result arraypairs.
- Initialize
-
- Iterate
ifrom0ton-1throughsortedSpells.
- Iterate
-
- Let
spell = sortedSpells[i][0]andoriginalIndex = sortedSpells[i][1].
- Let
-
- While
potionIndex >= 0and(long) spell * potions[potionIndex] >= success:
- While
-
-
Decrement `potionIndex`.
-
-
- The count of successful potions is
m - 1 - potionIndex.
- The count of successful potions is
-
- Set
pairs[originalIndex]to this count.
- Set
-
- Return
pairs.
- Return
Walkthrough
The main challenge with sorting spells is that the final result must correspond to the original order of spells. To handle this, we create a 2D array to store each spell's value along with its original index. First, we populate this new structure with (spells[i], i). We then sort this array based on the spell strength and also sort the potions array. We initialize two pointers: one for the sorted spells (say i, from 0 to n-1) and one for the potions array (say j, from m-1 down to 0). We iterate through the sorted spells. The key insight is that as we move to a stronger spell, the required potion strength decreases. Therefore, the j pointer only needs to move from right to left across the potions array and never needs to be reset. For each spell, we move the j pointer to the left (j--) as long as the product is successful. After the inner loop, j points to the strongest potion that is not successful. The number of successful potions is m - 1 - j. We store this count in the result array at the spell's original index, which we saved earlier. Because the j pointer traverses the potions array at most once, the scanning part is very efficient.
import java.util.Arrays;import java.util.Comparator; class Solution { public int[] successfulPairs(int[] spells, int[] potions, long success) { int n = spells.length; int m = potions.length; int[][] sortedSpells = new int[n][2]; for (int i = 0; i < n; i++) { sortedSpells[i][0] = spells[i]; sortedSpells[i][1] = i; } Arrays.sort(sortedSpells, Comparator.comparingInt(a -> a[0])); Arrays.sort(potions); int[] pairs = new int[n]; int potionIndex = m - 1; for (int i = 0; i < n; i++) { int spell = sortedSpells[i][0]; int originalIndex = sortedSpells[i][1]; while (potionIndex >= 0 && (long) spell * potions[potionIndex] >= success) { potionIndex--; } pairs[originalIndex] = m - 1 - potionIndex; } return pairs; }}Complexity
Time
O(n log n + m log m). O(n log n) for sorting spells, O(m log m) for sorting potions. The two-pointer scan takes O(n + m). The dominant part is the sorting.
Space
O(n) to store the `(spell, index)` pairs and the result array.
Trade-offs
Pros
Generally the most efficient approach, especially when n and m are of similar magnitude.
The two-pointer scan is very fast in practice due to linear memory access and fewer complex operations per step compared to binary search.
Cons
Requires extra space to store spell-index pairs.
The logic is the most complex of the three approaches.
A simple, straightforward approach that directly translates the problem statement into code. It iterates through each spell and, for each spell, iterates through all potions to check if their product meets the success threshold.
Algorithm
-
- Create an integer array
pairsof sizen.
- Create an integer array
-
- For each
spellat indexiinspells:
- For each
-
- Initialize
count = 0.
- Initialize
-
- For each
potionat indexjinpotions:
- For each
-
-
If `(long) spells[i] * potions[j] >= success`, increment `count`.
-
-
- Set
pairs[i] = count.
- Set
-
- Return
pairs.
- Return
Walkthrough
We initialize an answer array pairs of the same size as spells. We loop through each spell in the spells array using an index i. Inside this loop, we start a counter for successful pairs for the current spell. We then start a nested loop to iterate through every potion in the potions array. In the inner loop, we calculate the product of the current spell and potion. It's important to use a long data type for the product to prevent potential integer overflow, as success can be up to 10^10. If the product is greater than or equal to success, we increment the counter. After the inner loop finishes, we store the final count in pairs[i]. This process is repeated for all spells.
class Solution { public int[] successfulPairs(int[] spells, int[] potions, long success) { int n = spells.length; int m = potions.length; int[] pairs = new int[n]; for (int i = 0; i < n; i++) { int count = 0; for (int j = 0; j < m; j++) { if ((long) spells[i] * potions[j] >= success) { count++; } } pairs[i] = count; } return pairs; }}Complexity
Time
O(n * m), where `n` is the length of `spells` and `m` is the length of `potions`. The nested loops lead to a quadratic time complexity.
Space
O(n) to store the result array `pairs`. If the output array is not considered, the space complexity is O(1).
Trade-offs
Pros
Very simple to understand and implement.
Requires no modification of the input arrays.
Cons
Highly inefficient and will result in a 'Time Limit Exceeded' error for large inputs as specified in the constraints (n, m <= 10^5).
This approach optimizes the search for successful potions. Instead of linearly scanning the potions array for each spell, we first sort the potions array. Then, for each spell, we can efficiently find the number of successful potions using binary search.
Algorithm
-
- Sort the
potionsarray in ascending order.
- Sort the
-
- Create an integer array
pairsof sizen.
- Create an integer array
-
- For each
spellat indexiinspells:
- For each
-
- Calculate the minimum required potion strength:
min_potion = (success + spells[i] - 1) / spells[i].
- Calculate the minimum required potion strength:
-
- Perform a binary search on
potionsto find the leftmost indexkwherepotions[k] >= min_potion.
- Perform a binary search on
-
- The number of successful pairs is
m - k. Store this inpairs[i].
- The number of successful pairs is
-
- Return
pairs.
- Return
Walkthrough
The core idea is that for a given spell, if a certain potion is strong enough to form a successful pair, any potion stronger than it will also be successful. This property allows us to use binary search on a sorted potions array. First, we sort the potions array in non-decreasing order. Then, we iterate through each spell in the spells array. For each spell, we need to find the minimum potion strength required to meet the success threshold. This minimum strength is ceil(success / spell). In integer arithmetic, this can be calculated as (success + spell - 1) / spell to avoid floating-point issues. We then perform a binary search on the sorted potions array to find the index of the first potion whose strength is greater than or equal to this minimum required strength. Let's say the binary search returns an index k. This means potions[k] is the weakest potion that works with the current spell. Since the array is sorted, all potions from index k to m-1 will also work. The total number of successful potions is therefore m - k. We store this count in our result array for the current spell. If no potion is strong enough, the binary search will effectively point to an index m, resulting in a count of m - m = 0.
import java.util.Arrays; class Solution { public int[] successfulPairs(int[] spells, int[] potions, long success) { int n = spells.length; int m = potions.length; int[] pairs = new int[n]; Arrays.sort(potions); for (int i = 0; i < n; i++) { long minPotion = (success + spells[i] - 1) / spells[i]; // Binary search to find the first potion >= minPotion int left = 0; int right = m - 1; int index = m; // Default if no potion is strong enough while (left <= right) { int mid = left + (right - left) / 2; if ((long)potions[mid] >= minPotion) { index = mid; right = mid - 1; } else { left = mid + 1; } } pairs[i] = m - index; } return pairs; }}Complexity
Time
O(m log m + n log m). Sorting `potions` takes O(m log m). Then, for each of the `n` spells, we perform a binary search on `potions`, which takes O(log m).
Space
O(m) or O(log m) for sorting, depending on the implementation, plus O(n) for the output array.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
Passes the given constraints.
Cons
Requires modifying the
potionsarray by sorting it.The logic is more complex than brute force.
Solutions
Solution
class Solution {public int[] successfulPairs(int[] spells, int[] potions, long success) { Arrays.sort(potions); int n = spells.length, m = potions.length; int[] ans = new int[n]; for (int i = 0; i < n; ++i) { int left = 0, right = m; while (left < right) { int mid = (left + right) >> 1; if ((long)spells[i] * potions[mid] >= success) { right = mid; } else { left = mid + 1; } } ans[i] = m - left; } return ans; }}class Solution {public: vector<int> successfulPairs(vector<int> &spells, vector<int> &potions, long long success) { sort(potions.begin(), potions.end()); vector<int> ans; int m = potions.size(); for (int &v : spells) { int i = lower_bound(potions.begin(), potions.end(), success * 1.0 / v) - potions.begin(); ans.push_back(m - i); } return ans; }};class Solution: def successfulPairs(self, spells: List[int], potions: List[int], success: int) -> List[int]: potions . sort() m = len(potions) return [m - bisect_left(potions, success / v) for v in spells]Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.