2838. Maximum Coins Heroes Can Collect
Problem Description
In this battle scenario, there are n
heroes and m
monsters. Each hero and monster have their own power level, represented by two arrays heroes
and monsters
, respectively. The goal for each hero is to defeat monsters and collect coins. The number of coins that can be collected from defeating each monster is given in the array coins
. The key points to note:
- A hero can defeat a monster if the hero's power is equal to or greater than the monster's power.
- After a hero defeats a monster, they earn the number of coins associated with that monster.
- Heroes remain unharmed after battles, meaning their power levels don't decrease.
- Although multiple heroes can defeat the same monster, each monster yields coins to a given hero only once.
Given this setup, we need to determine the maximum number of coins each hero can earn in total from the battle.
Intuition
The intuitive approach to solving this problem involves maximizing the coins each hero can collect. To accomplish this, we want to pair each hero with monsters they are capable of defeating and ensure we do so in a way that maximizes the total coins earned.
Here's how we approach the solution:
- Sort the monsters by their power level while keeping track of their original indices so that we can match them with the corresponding coin values.
- Calculate the cumulative sum of coins in the order of sorted monsters. This allows us to easily determine the total coins collected up to any point in the sorted order of monsters.
- For each hero, find the rightmost monster in the sorted list that the hero can defeat. This step uses binary search to quickly find the position, as the sorted list allows for such a search.
- The cumulative sum up to the position found in step 3 gives us the maximum coins a hero can earn, since all prior monsters are weaker and thus defeatable by the hero.
By applying this strategy to all heroes, we create an array of the maximum coins collected by each hero, reflecting the optimal assignment of heroes to monsters based on their power levels.
Learn more about Two Pointers, Binary Search, Prefix Sum and Sorting patterns.
Solution Approach
The solution uses a few key Python features and algorithms to achieve the goal:
-
Sorting: First, we are sorting the monsters based on their power level using the
sorted()
function but with an additional twist. We sort the indices of the monsters array, not the values themselves. This allows us to maintain a correlation with thecoins
array. -
Cumulative Sum: We use the Python
itertools.accumulate()
function to compute the cumulative sum of the coins associated with each monster in increasing order of monster power. Theinitial=0
parameter ensures that we start with a zero value, representing that no coins are earned before defeating any monsters. -
Binary Search: We use the
bisect_right()
function from Python'sbisect
module to perform a binary search. This function is used to find the rightmost index at which the hero's power value (h
) would get inserted in the sorted monsters list to keep it in ascending order. Thus, this gives us the number of monsters that the current hero can defeat.- The binary search is made possible because we have sorted monsters by power, which allows us to effectively search for the largest set of monsters that a hero can defeat.
Putting it all together, the solution iterates over each hero's power and calculates the maximum coins they can collect using the precomputed cumulative sums and binary search:
idx
is the list of monster indices sorted by their power.s
is the cumulative sum of coins in the order of the sorted monster powers.- For each
h
(hero power) inheroes
, the binary search finds the number of monsters that the hero can defeat, ands[i]
gives the total coins earnable by defeating all monsters up to that point. - The
ans
list is populated with the maximum coins for each hero and returned at the end.
By following these steps, the function efficiently matches heroes with the optimal set of monsters, ensuring that each hero earns the maximum possible coins.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's use a small example to illustrate the solution approach with n
heroes and m
monsters. Suppose we have the following:
heroes = [5, 10, 3] monsters = [4, 9, 5, 8] coins = [3, 5, 2, 7]
Now let's step through the solution process.
- Sort monsters by their power level and maintain a correlation with their coin values.
Sorted monsters (by power): [4, 5, 8, 9] Corresponding indices: [0, 2, 3, 1] The indices array helps us to match monsters with the original `coins` array.
- Calculate the cumulative sum of coins based on sorted monsters’ power.
Using original coins: [3, 5, 2, 7] Corresponding to sorted powers: [3, 2, 7, 5] Cumulative sum: [3, 5 (3+2), 12 (3+2+7), 17 (3+2+7+5)]
- For each hero, perform a binary search to find the rightmost monster they can defeat.
For a hero with power 5:
Binary search will give index 1 (since the hero can defeat monsters with powers 4 and 5).
Hence, the maximum coins this hero can earn are the cumulative sum at index 1, which is 5.
For a hero with power 10:
Binary search will give index 3 (since the hero can defeat all monsters).
So the maximum coins this hero can earn are the cumulative sum at index 3, which is 17.
For a hero with power 3:
The hero cannot defeat any monster as the lowest monster power is 4.
Thus, the cumulative sum for this hero is 0.
- Compile the results into an array representing the maximum coins each hero can collect.
For heroes' powers: [5, 10, 3] The maximum coins they can collect are: [5, 17, 0]
This walkthrough should now provide a clear example of how the described solution approach is applied to solve the problem. Thus, by following each of these steps, we can determine the maximum number of coins that each hero can earn from defeating monsters.
Solution Implementation
1from itertools import accumulate
2from bisect import bisect_right
3from typing import List
4
5class Solution:
6 def maximumCoins(self, heroes: List[int], monsters: List[int], coins: List[int]) -> List[int]:
7 # Define the number of monsters
8 num_monsters = len(monsters)
9
10 # Create sorted indices of the monsters based on their strength
11 # The weakest monsters come first in the list
12 sorted_indices = sorted(range(num_monsters), key=lambda i: monsters[i])
13
14 # Calculate the cumulative sum of coins based on the sorted indices
15 # 's' will contain the cumulative coins we get after defeating monsters in sorted order
16 # The 'initial=0' argument ensures that there is a 0 at the beginning of the list
17 cumulative_coins = list(accumulate((coins[i] for i in sorted_indices), initial=0))
18
19 # Initialize a list to store the maximum coins that can be collected by each hero
20 collected_coins = []
21
22 # Iterate over each hero
23 for hero_strength in heroes:
24 # Find the furthest right position in the sorted monster list that the hero can defeat
25 # bisect_right returns the index where to insert hero_strength to keep the list sorted
26 monster_position = bisect_right(sorted_indices, hero_strength, key=lambda i: monsters[i])
27
28 # Append the cumulative coins up to that monster position for current hero's strength
29 collected_coins.append(cumulative_coins[monster_position])
30
31 # Return the list containing the maximum coins that each hero can collect
32 return collected_coins
33
1class Solution {
2 public long[] maximumCoins(int[] heroes, int[] monsters, int[] coins) {
3 int monsterCount = monsters.length;
4 Integer[] sortedIndices = new Integer[monsterCount];
5 // Initialize sortedIndices with array indices
6 for (int i = 0; i < monsterCount; ++i) {
7 sortedIndices[i] = i;
8 }
9
10 // Sort the indices based on the monsters' strength
11 Arrays.sort(sortedIndices, Comparator.comparingInt(j -> monsters[j]));
12
13 // Create a prefix sum array for coins based on sorted indices of monsters
14 long[] prefixSums = new long[monsterCount + 1];
15 for (int i = 0; i < monsterCount; ++i) {
16 prefixSums[i + 1] = prefixSums[i] + coins[sortedIndices[i]];
17 }
18
19 int heroCount = heroes.length;
20 long[] answer = new long[heroCount];
21
22 // For each hero, find their maximum possible collectable coins
23 for (int k = 0; k < heroCount; ++k) {
24 // Find the number of monsters a hero can defeat
25 int monsterDefeated = search(monsters, sortedIndices, heroes[k]);
26 // Assign the sum of coins from the monsters a hero can defeat
27 answer[k] = prefixSums[monsterDefeated];
28 }
29
30 return answer;
31 }
32
33 // Binary search to find the number of monsters a hero can defeat
34 private int search(int[] nums, Integer[] indices, int heroStrength) {
35 int left = 0, right = indices.length;
36 while (left < right) {
37 int mid = (left + right) >> 1;
38 // Check if the mid-point monster is stronger than the hero
39 if (nums[indices[mid]] > heroStrength) {
40 right = mid; // Look in the left subarray
41 } else {
42 left = mid + 1; // Look in the right subarray
43 }
44 }
45 // left now points to the number of monsters the hero can defeat
46 return left;
47 }
48}
49
1#include <vector>
2#include <numeric>
3#include <algorithm>
4
5using namespace std;
6
7class Solution {
8public:
9 vector<long long> maximumCoins(vector<int>& heroes, vector<int>& monsters, vector<int>& coins) {
10 // The number of monsters, used for setting up various bounds and loops
11 int monsterCount = monsters.size();
12
13 // Create a vector of indices corresponding to monster array positions
14 vector<int> monsterIndices(monsterCount);
15 iota(monsterIndices.begin(), monsterIndices.end(), 0);
16
17 // Sort the indices based on the monster strengths (from the monsters array),
18 // so we can later find out how many monsters a hero can defeat
19 sort(monsterIndices.begin(), monsterIndices.end(), [&](int i, int j) {
20 return monsters[i] < monsters[j];
21 });
22
23 // Prefix sum array to quickly calculate total coins up to a certain monster
24 long long prefixSum[monsterCount + 1];
25 prefixSum[0] = 0;
26 for (int i = 1; i <= monsterCount; ++i) {
27 prefixSum[i] = prefixSum[i - 1] + coins[monsterIndices[i - 1]];
28 }
29
30 // The answer vector to store maximum coins for each hero
31 vector<long long> answer;
32
33 // A lambda to search for the right-most position where a hero can defeat monsters
34 auto search = [&](int strength) {
35 int left = 0, right = monsterCount;
36 while (left < right) {
37 int mid = (left + right) >> 1;
38 if (monsters[monsterIndices[mid]] > strength) {
39 right = mid;
40 } else {
41 left = mid + 1;
42 }
43 }
44 return left;
45 };
46
47 // Use the search function defined above to calculate the total coins each hero can collect
48 for (int heroStrength : heroes) {
49 answer.push_back(prefixSum[search(heroStrength)]);
50 }
51
52 return answer;
53 }
54};
55
56// Note: Although this code compiles and adheres to standard C++ syntax, without additional context it is unclear what this algorithm is intended to solve.
57// It seems to match heroes against a sorted list of monsters by their strength and calculate the maximum coins each hero can collect based on the monsters they can defeat.
58
1function maximumCoins(heroes: number[], monsters: number[], coins: number[]): number[] {
2 // Length of the monsters array
3 const numberOfMonsters = monsters.length;
4
5 // Create an index array from 0 to numberOfMonsters-1
6 const indices: number[] = Array.from({ length: numberOfMonsters }, (_, i) => i);
7
8 // Sort the indices array based on the corresponding value in the monsters array
9 indices.sort((a, b) => monsters[a] - monsters[b]);
10
11 // 'prefixSum' represents cumulative coins amount from monsters sorted on strength
12 const prefixSum: number[] = Array(numberOfMonsters + 1).fill(0);
13 for (let i = 0; i < numberOfMonsters; ++i) {
14 prefixSum[i + 1] = prefixSum[i] + coins[indices[i]];
15 }
16
17 // Binary search helper method that finds how many monsters a hero can defeat
18 const searchMonsters = (strength: number): number => {
19 let low = 0;
20 let high = numberOfMonsters;
21
22 // Look for the rightmost monster that hero can defeat
23 while (low < high) {
24 const mid = (low + high) >> 1; // Equivalent to Math.floor((low + high) / 2)
25 if (monsters[indices[mid]] > strength) {
26 high = mid;
27 } else {
28 low = mid + 1;
29 }
30 }
31 return low;
32 };
33
34 // Map heroes to their maximum coins earnings based on which monsters they can defeat
35 return heroes.map(heroStrength => prefixSum[searchMonsters(heroStrength)]);
36}
37
Time and Space Complexity
Time Complexity
The time complexity of the code can be broken down into the following parts:
- Sorting the index list
idx
: This takesO(m log m)
time, wherem
is the length of themonsters
list. - Creating the
s
list with accumulated coins: Theaccumulate
function runs inO(m)
since it processes each element once. - The for loop to fill
ans
list: For each hero inheroes
, a binary search is performed usingbisect_right
, which takesO(log m)
. Letn
be the length of theheroes
list, so the loop runs inO(n log m)
time. - Combining these, the total time complexity is
O(m log m + m + n log m)
which simplifies toO(m log m + n log m)
because them
term is dominated by them log m
term.
Space Complexity
The space complexity can be analyzed as follows:
- The
idx
list takesO(m)
space. - The
s
list also takesO(m)
space. - The
ans
list takesO(n)
space, wheren
is the length of theheroes
list. - Temporary variables used inside the for loop take constant space.
- Thus, the total space complexity is
O(m + n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Want a Structured Path to Master System Design Too? Don’t Miss This!