2681. Power of Heroes
Problem Description
You are given a 0-indexed integer array nums
representing the strength of some heroes. Your task is to find the sum of the power of all possible non-empty groups of heroes.
The power of a group of heroes is calculated using a specific formula:
- For a group with heroes at indices
i₀, i₁, ..., iₖ
, the power is defined as:max(nums[i₀], nums[i₁], ..., nums[iₖ])² × min(nums[i₀], nums[i₁], ..., nums[iₖ])
- In other words, you take the maximum strength value in the group, square it, and multiply by the minimum strength value in the group
You need to:
- Consider all possible non-empty subsets of the array (not necessarily contiguous)
- Calculate the power for each subset using the formula above
- Sum up all these power values
- Return the sum modulo
10⁹ + 7
(since the result can be very large)
For example, if you have a group with strengths [2, 3, 5], the power would be: 5² × 2 = 25 × 2 = 50
(where 5 is the maximum and 2 is the minimum).
The challenge involves efficiently computing this sum across all possible subsets, as the naive approach of generating all subsets would be too slow for large arrays.
Intuition
The key insight is that since we're dealing with maximum and minimum values of subsets, the order of elements in the original array doesn't matter - we can sort it first to make our calculations easier.
Once sorted, let's think about how to count contributions efficiently. For any subset, the power formula is max² × min
. If we fix the minimum element, we need to consider all possible subsets that include this minimum and potentially larger elements.
Consider a sorted array [a₁, a₂, a₃, ..., aₙ]
. If we fix aᵢ
as the minimum element of a subset, then:
- The subset can include
aᵢ
itself (power =aᵢ² × aᵢ = aᵢ³
) - The subset can include
aᵢ
andaᵢ₊₁
(power =aᵢ₊₁² × aᵢ
) - The subset can include
aᵢ
andaᵢ₊₂
(power =aᵢ₊₂² × aᵢ
) - And so on...
But wait - we can also have subsets like {aᵢ, aᵢ₊₁, aᵢ₊₂}
where the maximum is aᵢ₊₂
. How many such subsets are there? For each element aⱼ
where j > i
, it can be the maximum element in 2^(j-i-1)
different subsets (we can choose to include or exclude each element between i
and j
).
This means when aᵢ
is the minimum, the total contribution is:
aᵢ × (aᵢ² + aᵢ₊₁² + 2×aᵢ₊₂² + 4×aᵢ₊₃² + ... + 2^(n-i-1)×aₙ²)
The coefficient doubles each time because each subsequent element can be the maximum in twice as many subsets as the previous one.
To compute this efficiently, we can process the array from right to left, maintaining a running sum p
that represents the weighted sum of squares. For each element, we:
- Add its contribution when it's the minimum (
aᵢ × p
) - Add its contribution when it's both min and max (
aᵢ³
) - Update
p
for the next iteration:p = 2×p + aᵢ²
This way, we avoid recalculating the weighted sums repeatedly and achieve a linear time solution after sorting.
Learn more about Math, Dynamic Programming, Prefix Sum and Sorting patterns.
Solution Approach
Based on our intuition, let's implement the solution step by step:
Step 1: Sort the array Since the order doesn't affect the result and we need to work with minimum and maximum values, we first sort the array in ascending order.
Step 2: Initialize variables
mod = 10^9 + 7
for the modulo operationans = 0
to accumulate the total power sump = 0
to maintain the weighted sum of squares for efficient calculation
Step 3: Process elements from right to left
We iterate through the sorted array in reverse order (from largest to smallest). For each element x
:
-
Add the contribution when
x
is the minimum element:- When
x
is both min and max (single element subset):x³
- This is computed as:
ans = (ans + x² × x) % mod
- When
-
Add the contribution from larger elements:
- When
x
is minimum and there are larger elements as maximum:x × p
- This is computed as:
ans = (ans + x × p) % mod
- When
-
Update
p
for the next iteration:- The formula
p = 2×p + x²
maintains the weighted sum - Each existing term in
p
gets doubled (representing the doubling of subset counts) - We add
x²
for the current element
- The formula
Why this works:
When we process element aᵢ
as the minimum:
- The value
p
already contains the weighted sum:aᵢ₊₁² + 2×aᵢ₊₂² + 4×aᵢ₊₃² + ...
- Multiplying
aᵢ × p
gives us all contributions whereaᵢ
is minimum and some larger element is maximum - Adding
aᵢ³
accounts for the subset containing onlyaᵢ
- Updating
p = 2×p + aᵢ²
prepares it for the next element:- The
2×p
part doubles all existing coefficients - Adding
aᵢ²
includes the current element for future calculations
- The
Time Complexity: O(n log n)
due to sorting, where n is the length of the array
Space Complexity: O(1)
excluding the space used for sorting
The modulo operation is applied at each step to prevent integer overflow, ensuring the result stays within bounds.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small example: nums = [1, 2, 3]
Step 1: Sort the array
Array is already sorted: [1, 2, 3]
Step 2: Initialize variables
mod = 10^9 + 7
ans = 0
(total power sum)p = 0
(weighted sum of squares)
Step 3: Process from right to left
Iteration 1: Process element 3 (index 2)
- Current element
x = 3
- Add contribution when 3 is minimum (only subset {3}):
ans = ans + 3² × 3 = 0 + 27 = 27
- Add contribution from larger elements (none exist):
ans = ans + 3 × p = 27 + 3 × 0 = 27
- Update p for next iteration:
p = 2 × p + 3² = 2 × 0 + 9 = 9
Iteration 2: Process element 2 (index 1)
- Current element
x = 2
- Add contribution when 2 is minimum:
- Single element subset {2}:
2³ = 8
ans = ans + 2² × 2 = 27 + 8 = 35
- Single element subset {2}:
- Add contribution from larger elements:
- Subset {2, 3} has power 3² × 2 = 18
- This is captured by:
ans = ans + 2 × p = 35 + 2 × 9 = 53
- Update p for next iteration:
p = 2 × p + 2² = 2 × 9 + 4 = 22
Iteration 3: Process element 1 (index 0)
- Current element
x = 1
- Add contribution when 1 is minimum:
- Single element subset {1}:
1³ = 1
ans = ans + 1² × 1 = 53 + 1 = 54
- Single element subset {1}:
- Add contribution from larger elements:
- Subset {1, 2}: power = 2² × 1 = 4
- Subset {1, 3}: power = 3² × 1 = 9
- Subset {1, 2, 3}: power = 3² × 1 = 9
- Total from p: 4 + 9 + 9 = 22
ans = ans + 1 × p = 54 + 1 × 22 = 76
Final answer: 76
Let's verify by listing all subsets and their powers:
- {1}: 1² × 1 = 1
- {2}: 2² × 2 = 8
- {3}: 3² × 3 = 27
- {1, 2}: 2² × 1 = 4
- {1, 3}: 3² × 1 = 9
- {2, 3}: 3² × 2 = 18
- {1, 2, 3}: 3² × 1 = 9
Total: 1 + 8 + 27 + 4 + 9 + 18 + 9 = 76 ✓
The key insight is how p
accumulates the weighted sum of squares. When processing element 2, p = 9
represents that element 3 can be the maximum in exactly one subset where 2 is minimum. When processing element 1, p = 22
represents:
- Element 2 can be max in 1 subset: 2² × 1 = 4
- Element 3 can be max in 2 subsets ({1,3} and {1,2,3}): 2 × 3² × 1 = 18
- Total: 4 + 18 = 22
Solution Implementation
1class Solution:
2 def sumOfPower(self, nums: List[int]) -> int:
3 MOD = 10**9 + 7
4
5 # Sort numbers in ascending order
6 nums.sort()
7
8 # Initialize result and prefix sum tracker
9 total_sum = 0
10 prefix_contribution = 0
11
12 # Process numbers from largest to smallest
13 for current_num in reversed(nums):
14 # Add contribution where current_num is the maximum
15 # This includes current_num^3 (when it's alone)
16 total_sum = (total_sum + (current_num * current_num % MOD) * current_num) % MOD
17
18 # Add contribution from subsequences where current_num is minimum
19 # and other elements form the rest of the subsequence
20 total_sum = (total_sum + current_num * prefix_contribution) % MOD
21
22 # Update prefix contribution for next iteration
23 # Each previous contribution doubles (can include or exclude current)
24 # Plus new contribution from current_num^2
25 prefix_contribution = (prefix_contribution * 2 + current_num * current_num) % MOD
26
27 return total_sum
28
1class Solution {
2 public int sumOfPower(int[] nums) {
3 // Define modulo constant for preventing integer overflow
4 final int MOD = 1_000_000_007;
5
6 // Sort the array in ascending order
7 Arrays.sort(nums);
8
9 // Initialize variables
10 long totalSum = 0; // Stores the final answer
11 long prefixContribution = 0; // Stores accumulated contribution from previous elements
12
13 // Process elements from largest to smallest
14 for (int i = nums.length - 1; i >= 0; i--) {
15 long currentValue = nums[i];
16
17 // Add contribution where current element is the maximum
18 // This represents subsequences ending at current element
19 long currentSquared = currentValue * currentValue % MOD;
20 totalSum = (totalSum + currentSquared * currentValue) % MOD;
21
22 // Add contribution from subsequences where current element is the minimum
23 // and previous elements form the middle part
24 totalSum = (totalSum + currentValue * prefixContribution % MOD) % MOD;
25
26 // Update prefix contribution for next iteration
27 // Each previous subsequence can either include or exclude current element
28 // Hence multiply by 2, plus new subsequences starting with current element
29 prefixContribution = (prefixContribution * 2 + currentSquared) % MOD;
30 }
31
32 return (int) totalSum;
33 }
34}
35
1class Solution {
2public:
3 int sumOfPower(vector<int>& nums) {
4 const int MOD = 1000000007; // 10^9 + 7 for modular arithmetic
5
6 // Sort array in descending order
7 sort(nums.rbegin(), nums.rend());
8
9 long long totalSum = 0; // Accumulates the final answer
10 long long prefixContribution = 0; // Tracks contribution from previously processed elements
11
12 // Process each number from largest to smallest
13 for (long long currentNum : nums) {
14 // Add contribution where current number is the maximum
15 // This adds currentNum^3 (currentNum as both max and min in single-element subsequence)
16 totalSum = (totalSum + (currentNum * currentNum % MOD) * currentNum) % MOD;
17
18 // Add contribution from subsequences where current number is the minimum
19 // and previous numbers form the maximum
20 totalSum = (totalSum + currentNum * prefixContribution % MOD) % MOD;
21
22 // Update prefix contribution for next iteration
23 // Each previous subsequence can either include or exclude the current number
24 // So we double the previous contribution and add currentNum^2 for new subsequences
25 prefixContribution = (prefixContribution * 2 + currentNum * currentNum % MOD) % MOD;
26 }
27
28 return static_cast<int>(totalSum);
29 }
30};
31
1/**
2 * Calculates the sum of power of all non-empty subsequences of nums
3 * Power is defined as (max element)^2 * (min element)
4 * @param nums - Array of positive integers
5 * @returns Sum of powers modulo 10^9 + 7
6 */
7function sumOfPower(nums: number[]): number {
8 const MOD = 10 ** 9 + 7;
9
10 // Sort the array in ascending order
11 nums.sort((a, b) => a - b);
12
13 let totalSum = 0;
14 // Tracks the contribution from previous elements
15 // Represents sum of (min * max^2) for subsequences ending before current element
16 let previousContribution = 0;
17
18 // Process elements from largest to smallest
19 for (let i = nums.length - 1; i >= 0; --i) {
20 const currentValue = BigInt(nums[i]);
21
22 // Add contribution when current element is both max and min (single element subsequence)
23 // Power = currentValue^3
24 totalSum = (totalSum + Number((currentValue * currentValue * currentValue) % BigInt(MOD))) % MOD;
25
26 // Add contribution from subsequences where current element is the minimum
27 // and some larger element (already processed) is the maximum
28 totalSum = (totalSum + Number((currentValue * BigInt(previousContribution)) % BigInt(MOD))) % MOD;
29
30 // Update previous contribution for next iteration
31 // Each existing subsequence can either include or exclude the current element
32 // So we multiply by 2, then add new subsequences with current as max
33 previousContribution = Number((BigInt(previousContribution) * 2n + currentValue * currentValue) % BigInt(MOD));
34 }
35
36 return totalSum;
37}
38
Time and Space Complexity
The time complexity is O(n × log n)
, where n
is the length of the array. This is dominated by the sorting operation nums.sort()
, which takes O(n × log n)
time. The subsequent loop iterates through the array once in O(n)
time, performing constant-time operations within each iteration.
The space complexity is O(log n)
. While the code doesn't explicitly create additional data structures proportional to the input size, the sorting algorithm (typically Timsort in Python) requires O(log n)
space for its recursive call stack. The variables ans
, p
, and mod
use only O(1)
additional space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow in Intermediate Calculations
The most critical pitfall in this problem is handling integer overflow during intermediate calculations, particularly when computing current_num * current_num * current_num
or current_num * prefix_contribution
.
The Problem: Even though we're using modulo operations, the intermediate multiplication can overflow before the modulo is applied. For example:
- If
current_num = 10^5
, thencurrent_num^3 = 10^15
, which exceeds the typical integer limit - The expression
(current_num * current_num % MOD) * current_num
might still overflow if not handled carefully
Solution: Apply modulo operations more frequently to keep intermediate values small:
# Instead of: total_sum = (total_sum + (current_num * current_num % MOD) * current_num) % MOD # Use: squared = (current_num * current_num) % MOD cubed = (squared * current_num) % MOD total_sum = (total_sum + cubed) % MOD
2. Incorrect Order of Operations with Modulo
Another common mistake is applying modulo operations in the wrong order or missing them in certain places.
The Problem: Forgetting to apply modulo to intermediate results can lead to overflow, while applying it incorrectly can produce wrong results.
Solution: Ensure modulo is applied consistently after each arithmetic operation:
class Solution:
def sumOfPower(self, nums: List[int]) -> int:
MOD = 10**9 + 7
nums.sort()
total_sum = 0
prefix_contribution = 0
for current_num in reversed(nums):
# Break down calculations to avoid overflow
curr_squared = (current_num * current_num) % MOD
curr_cubed = (curr_squared * current_num) % MOD
# Add current element's contribution
total_sum = (total_sum + curr_cubed) % MOD
# Add prefix contribution
contribution = (current_num * prefix_contribution) % MOD
total_sum = (total_sum + contribution) % MOD
# Update prefix for next iteration
prefix_contribution = (2 * prefix_contribution + curr_squared) % MOD
return total_sum
3. Misunderstanding the Prefix Contribution Update
The formula prefix_contribution = (2 * prefix_contribution + current_num^2)
is crucial but easy to get wrong.
The Problem:
- Forgetting to multiply by 2 (which represents the doubling of subset counts)
- Adding
current_num
instead ofcurrent_num^2
- Updating prefix_contribution before using it in calculations
Solution:
Always update prefix_contribution
after using it in the current iteration's calculations, and ensure you're adding the square of the current number.
4. Processing Order Confusion
Processing the array in the wrong direction can lead to incorrect results.
The Problem: The algorithm relies on processing elements from largest to smallest (after sorting) to correctly accumulate the prefix contributions.
Solution:
Always sort first, then process in reverse order using reversed(nums)
or iterate with reverse indices.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!