Successful Pairs of Spells and Potions
MEDIUMDescription
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
Checkout 3 different approaches to solve Successful Pairs of Spells and Potions. Click on different approaches to view the approach and algorithm in detail.
Sorting Both Arrays with Two Pointers
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
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 Analysis
Pros and Cons
- 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.
- Requires extra space to store spell-index pairs.
- The logic is the most complex of the three approaches.
Code Solutions
Checking out 3 solutions in different languages for Successful Pairs of Spells and Potions. Click on different languages to view the code.
Video Solution
Watch the video walkthrough for Successful Pairs of Spells and Potions
Similar Questions
5 related questions you might find useful
Algorithms:
Patterns:
Data Structures:
Companies:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.