Facebook Pixel

3627. Maximum Median Sum of Subsequences of Size 3

Problem Description

You have an integer array nums whose length is divisible by 3. Your goal is to empty this array through a series of steps, and in each step you must:

  1. Select any three elements from the array
  2. Calculate the median of these three elements
  3. Remove all three selected elements from the array

The median of three numbers is the middle value when they are arranged in ascending order. For example, the median of [5, 2, 8] is 5 (since when sorted it becomes [2, 5, 8]).

You need to find the maximum possible sum of all the medians you can obtain through this process.

Since the array length is divisible by 3, you can perfectly divide the array into groups of three elements. The challenge is to strategically select which elements to group together in each step to maximize the total sum of medians.

For example, if nums = [1, 2, 3, 4, 5, 6], you could:

  • Select [1, 3, 5] with median 3, and [2, 4, 6] with median 4, giving sum = 3 + 4 = 7
  • Or select [1, 2, 3] with median 2, and [4, 5, 6] with median 5, giving sum = 2 + 5 = 7
  • Or other combinations that might yield different sums

The key insight is that to maximize the sum, you want larger numbers to serve as medians. By sorting the array and carefully selecting elements starting from position n/3 and taking every second element, you ensure that smaller elements are used as the minimum values in each triplet while larger elements become the medians.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Let's think about what happens when we select three elements and take their median. The median is always the middle value, which means one element will be smaller than it, and one will be larger. The smaller and larger elements essentially get "wasted" - they don't contribute to our sum.

Since we want to maximize the sum of medians, we should aim to make the largest possible numbers serve as medians. But here's the catch: for a number to be a median, we need at least one number smaller than it in the same group.

Consider a sorted array. If we have n elements total, we'll form n/3 groups of three. This means we'll have n/3 medians to sum up.

Now, which n/3 elements should ideally be our medians? We'd want the largest n/3 elements! But can we actually achieve this?

The key realization is that the smallest n/3 elements in our sorted array can serve as the "minimum elements" for each of our n/3 groups. This allows the next n/3 elements to potentially be medians, and the largest n/3 elements to be the maximum elements in each group.

But wait - we can do even better! Instead of using elements at positions [n/3, n/3+1, ..., 2n/3-1] as medians, we can be more strategic. If we pair each small element with two larger elements carefully, we can actually use elements at positions [n/3, n/3+2, n/3+4, ...] as medians.

The pattern emerges: after sorting, start from index n/3 and pick every second element. This works because:

  • Elements before index n/3 serve as the minimum values in each triplet
  • The selected elements (at positions n/3, n/3+2, n/3+4, etc.) become medians
  • The skipped elements become the maximum values in the triplets

This greedy approach guarantees we're selecting the largest possible set of medians while ensuring each can actually serve as a median in some valid triplet.

Solution Approach

The solution implements the greedy strategy we identified through a simple and elegant approach:

  1. Sort the array: First, we sort nums in ascending order. This allows us to systematically identify which elements should serve as medians.

    nums.sort()
  2. Select medians using slicing: After sorting, we use Python's array slicing to select our medians. The key insight is using the slice nums[len(nums) // 3 :: 2].

    Let's break down this slice notation:

    • len(nums) // 3: This is our starting index. We skip the first n/3 elements because they'll serve as the minimum values in our triplets.
    • :: 2: This means "take every second element" starting from our starting position.
  3. Sum the selected elements: We simply sum all the selected elements, which represent our medians.

    return sum(nums[len(nums) // 3 :: 2])

Example walkthrough: If nums = [1, 2, 3, 4, 5, 6]:

  • After sorting: [1, 2, 3, 4, 5, 6]
  • len(nums) // 3 = 6 // 3 = 2
  • nums[2::2] selects elements at indices 2, 4 → values [3, 5]
  • Sum = 3 + 5 = 8

This means we're implicitly forming groups like:

  • Group 1: [1, 3, 4] with median 3
  • Group 2: [2, 5, 6] with median 5

The time complexity is O(n log n) due to sorting, and the space complexity is O(1) if we don't count the space used by the sorting algorithm.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with nums = [9, 3, 1, 7, 2, 8, 5, 4, 6].

Step 1: Sort the array After sorting: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Step 2: Identify the starting position

  • Array length = 9
  • Starting index = 9 // 3 = 3
  • We'll select medians starting from index 3

Step 3: Select every second element from position 3

  • Index 3: value = 4
  • Index 5: value = 6
  • Index 7: value = 8

Selected medians: [4, 6, 8]

Step 4: Form the actual triplets To understand why this works, let's see how these elements become medians:

  • Triplet 1: [1, 4, 5] → sorted: [1, 4, 5] → median = 4 ✓
  • Triplet 2: [2, 6, 7] → sorted: [2, 6, 7] → median = 6 ✓
  • Triplet 3: [3, 8, 9] → sorted: [3, 8, 9] → median = 8 ✓

Notice the pattern:

  • The smallest n/3 elements [1, 2, 3] serve as minimum values
  • Our selected elements [4, 6, 8] become medians
  • The remaining elements [5, 7, 9] serve as maximum values

Step 5: Calculate the sum Maximum sum of medians = 4 + 6 + 8 = 18

This greedy approach ensures we get the largest possible medians by strategically pairing small elements (as minimums) with larger elements, allowing middle-range values to serve as medians while preserving the largest values for use as maximums in the triplets.

Solution Implementation

1class Solution:
2    def maximumMedianSum(self, nums: List[int]) -> int:
3        # Sort the array in ascending order
4        nums.sort()
5      
6        # Starting from index len(nums)//3, take every other element
7        # This selects elements that will be medians in optimal grouping
8        # For an array of length n, we form n/3 groups of 3 elements each
9        # To maximize median sum, we want largest possible values as medians
10        # By taking elements from position n/3 with step 2, we get:
11        # - Elements at positions n/3, n/3+2, n/3+4, ... as medians
12        # - This ensures each selected element can be a median of a unique group
13        median_elements = nums[len(nums) // 3 :: 2]
14      
15        # Return the sum of all selected median elements
16        return sum(median_elements)
17
1class Solution {
2    public long maximumMedianSum(int[] nums) {
3        // Sort the array in ascending order
4        Arrays.sort(nums);
5      
6        // Get the length of the array
7        int n = nums.length;
8      
9        // Initialize the sum of medians
10        long ans = 0;
11      
12        // Start from index n/3 and take every other element
13        // This ensures we pick the median values that maximize the sum
14        // when dividing the sorted array into groups of 3
15        for (int i = n / 3; i < n; i += 2) {
16            ans += nums[i];
17        }
18      
19        // Return the maximum sum of medians
20        return ans;
21    }
22}
23
1class Solution {
2public:
3    long long maximumMedianSum(vector<int>& nums) {
4        // Sort the array in ascending order to arrange elements optimally
5        ranges::sort(nums);
6      
7        // Get the size of the input array
8        int n = nums.size();
9      
10        // Initialize the sum of medians
11        long long ans = 0;
12      
13        // Start from index n/3 and pick every other element
14        // These elements will serve as medians in the optimal grouping
15        // We skip the smallest n/3 elements to use them as the minimum values in groups
16        for (int i = n / 3; i < n; i += 2) {
17            ans += nums[i];
18        }
19      
20        // Return the maximum sum of medians
21        return ans;
22    }
23};
24
1/**
2 * Calculates the maximum sum of medians from subarrays of length 3
3 * @param nums - The input array of numbers
4 * @returns The maximum sum of medians
5 */
6function maximumMedianSum(nums: number[]): number {
7    // Sort the array in ascending order
8    nums.sort((a: number, b: number) => a - b);
9  
10    // Get the length of the array
11    const arrayLength: number = nums.length;
12  
13    // Initialize the result sum
14    let maxSum: number = 0;
15  
16    // Start from n/3 position and pick every other element
17    // This greedy approach ensures we get the maximum possible medians
18    for (let index: number = Math.floor(arrayLength / 3); index < arrayLength; index += 2) {
19        maxSum += nums[index];
20    }
21  
22    return maxSum;
23}
24

Time and Space Complexity

The time complexity is O(n × log n), where n is the length of the array nums. This is dominated by the sorting operation nums.sort(), which uses Timsort algorithm in Python with O(n × log n) time complexity in the average and worst cases. The slicing operation nums[len(nums) // 3 :: 2] takes O(n) time to create a new list, and the sum() operation also takes O(n) time in the worst case. Since O(n × log n) + O(n) + O(n) = O(n × log n), the overall time complexity is O(n × log n).

The space complexity is O(log n), where n is the length of the array nums. The sorting operation nums.sort() is an in-place sort that uses O(log n) space for the recursion stack in Timsort. The slicing operation nums[len(nums) // 3 :: 2] creates a new list that contains at most n/2 elements, which would be O(n) space. However, considering that Python's built-in sort dominates with O(log n) auxiliary space for in-place sorting, and the reference answer indicates O(log n), this suggests the analysis focuses on the auxiliary space used by the sorting algorithm itself, not including the output slice which is part of the return value computation.

Common Pitfalls

1. Misunderstanding the Slicing Logic

A common mistake is incorrectly implementing the slicing pattern, particularly confusing the starting index or step size. Some might write:

  • nums[len(nums) // 3:] (forgetting the step of 2)
  • nums[::2] (starting from index 0 instead of n/3)
  • nums[len(nums) // 3 + 1::2] (off-by-one error in starting position)

Solution: Remember that the slice nums[len(nums) // 3 :: 2] specifically:

  • Starts at index n/3 (not n/3 + 1 or 0)
  • Takes every second element (step = 2)
  • This ensures exactly n/3 elements are selected as medians

2. Attempting to Form Explicit Groups

Some developers try to explicitly form triplets and calculate medians, leading to unnecessarily complex code:

# Overly complex approach
groups = []
for i in range(0, len(nums), 3):
    groups.append(sorted(nums[i:i+3]))
return sum(group[1] for group in groups)

Solution: The mathematical insight allows us to avoid explicit grouping. After sorting, we know that optimal medians are at positions n/3, n/3+2, n/3+4, ... regardless of how we actually form the groups.

3. Forgetting to Sort First

Without sorting, the slicing approach becomes meaningless:

# Wrong: operating on unsorted array
return sum(nums[len(nums) // 3 :: 2])

Solution: Always sort first. The entire strategy depends on having elements in ascending order to identify which values should serve as medians.

4. Integer Division Confusion

In languages with different division operators, using regular division instead of integer division can cause errors:

# Potential issue in Python 2 or other languages
nums[len(nums) / 3 :: 2]  # May result in float index

Solution: Always use integer division (// in Python 3) to ensure the index is an integer.

5. Not Validating Input Assumptions

The problem states the array length is divisible by 3, but in practice, you might want to validate this:

if len(nums) % 3 != 0:
    # Handle edge case

Solution: While the problem guarantees this condition, adding validation can prevent runtime errors during testing or if the problem constraints change.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More