2592. Maximize Greatness of an Array
Problem Description
You have an integer array nums
with 0-based indexing. Your task is to rearrange this array into a new permutation called perm
.
The greatness of the array is defined as the count of indices i
(where 0 <= i < nums.length
) for which perm[i] > nums[i]
. In other words, greatness measures how many positions have a value in the permuted array that is strictly greater than the value at the same position in the original array.
Your goal is to find a permutation of nums
that maximizes this greatness value and return the maximum possible greatness.
For example, if nums = [1, 3, 5, 2, 1, 3, 1]
, you could rearrange it to perm = [2, 5, 1, 3, 3, 1, 1]
. Comparing position by position:
- Position 0:
2 > 1
β - Position 1:
5 > 3
β - Position 2:
1 < 5
β - Position 3:
3 > 2
β - Position 4:
3 > 1
β - Position 5:
1 < 3
β - Position 6:
1 = 1
β
This gives a greatness of 4 (positions 0, 1, 3, and 4 satisfy the condition).
The solution uses a greedy approach by sorting the array first. It maintains a pointer i
starting at index 0. As it iterates through each element x
in the sorted array, if x
is greater than nums[i]
, it increments i
. The final value of i
represents the maximum greatness achievable, as it counts how many elements from the sorted array can be matched with smaller elements to create the perm[i] > nums[i]
condition.
Intuition
To maximize greatness, we need to pair each element in the original array with a larger element from the same array in the permuted version. The key insight is that we want to use the smallest possible "larger" element for each position to avoid wasting our larger values.
Think of it this way: if we have a value 3
in the original array, we could pair it with 7
or 4
from our available elements. Using 4
instead of 7
is better because we save 7
for potentially pairing with a larger value later.
This naturally leads us to sort the array first. Once sorted, we can use a two-pointer approach where we try to create the most efficient pairings.
Consider the sorted array as two roles:
- The "original positions" that need to be beaten
- The "candidates" that we can use to beat them
We start with the smallest element in the "original positions" and try to find the smallest element from our "candidates" that can beat it. If we find one, we move to the next position to beat and the next candidate. If the current candidate can't beat the current position, we skip that candidate and try the next one.
For example, with sorted array [1, 1, 2, 3, 3, 5]
:
- Can we beat
1
(at index 0)? Yes, with2
β move both pointers - Can we beat
1
(at index 1)? Yes, with3
β move both pointers - Can we beat
2
(at index 2)? Yes, with3
β move both pointers - Can we beat
3
(at index 3)? Yes, with5
β move both pointers - No more candidates left
This greedy strategy ensures we maximize the number of positions where perm[i] > nums[i]
by always using the smallest sufficient value, preserving larger values for later use.
Learn more about Greedy, Two Pointers and Sorting patterns.
Solution Approach
The implementation follows a greedy algorithm with these steps:
-
Sort the array: First, we sort
nums
in ascending order. This allows us to efficiently match smaller elements with their smallest possible "greater" counterparts.nums.sort()
-
Initialize a pointer: We use a single pointer
i
starting at position 0. This pointer tracks the current position in the sorted array that we're trying to "beat" (find a larger element for).i = 0
-
Traverse and match elements: We iterate through each element
x
in the sorted array. Eachx
serves as a potential candidate to beat the element at positioni
:for x in nums: i += x > nums[i]
The clever part here is the expression
x > nums[i]
:- If
x > nums[i]
, the comparison returnsTrue
(which equals 1 in Python), soi
increments by 1 - If
x <= nums[i]
, the comparison returnsFalse
(which equals 0), soi
stays the same
When we increment
i
, it means we've successfully found a pairing where the permuted value beats the original value. We then move to the next position to beat. - If
-
Return the result: The final value of
i
represents the maximum number of positions where we achievedperm[i] > nums[i]
.
Why this works: By processing elements in sorted order, we ensure that:
- We always try to use the smallest possible element that can beat the current position
- We never waste larger elements on positions that could be beaten by smaller ones
- The pointer
i
only advances when we make a successful pairing, automatically counting our greatness
Time Complexity: O(n log n)
due to sorting, where n
is the length of the array.
Space Complexity: O(1)
if we don't count the space used for sorting (in-place sorting), or O(n)
if we consider the sorting space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [2, 1, 3, 1]
.
Step 1: Sort the array
- Original:
[2, 1, 3, 1]
- Sorted:
[1, 1, 2, 3]
Step 2: Initialize pointer
i = 0
(pointing to the first element we want to "beat")
Step 3: Iterate through sorted array
Iteration | Current x | nums[i] | Is x > nums[i] ? | Action | New i |
---|---|---|---|---|---|
1 | 1 | nums[0] = 1 | 1 > 1? No | i stays same | 0 |
2 | 1 | nums[0] = 1 | 1 > 1? No | i stays same | 0 |
3 | 2 | nums[0] = 1 | 2 > 1? Yes | i increments | 1 |
4 | 3 | nums[1] = 1 | 3 > 1? Yes | i increments | 2 |
Step 4: Return result
- Final
i = 2
, so maximum greatness is 2
Understanding the result:
- We successfully paired:
- Position 0 (value 1) with element 2 β 2 > 1 β
- Position 1 (value 1) with element 3 β 3 > 1 β
- We couldn't beat positions 2 and 3 because we used up our larger elements
This means we can create a permutation like [2, 3, 1, 1]
when mapped back to original indices, achieving a greatness of 2 (though the actual permutation construction isn't part of this algorithm - we only calculate the maximum achievable greatness).
Solution Implementation
1class Solution:
2 def maximizeGreatness(self, nums: List[int]) -> int:
3 # Sort the array to enable greedy matching
4 nums.sort()
5
6 # Initialize pointer for the smaller elements to be upgraded
7 left_pointer = 0
8
9 # Iterate through each element as a potential larger element
10 for current_value in nums:
11 # If current element can upgrade the element at left_pointer
12 # Move the left_pointer forward (counting one successful upgrade)
13 left_pointer += current_value > nums[left_pointer]
14
15 # The final position of left_pointer equals the number of successful upgrades
16 return left_pointer
17
1class Solution {
2 public int maximizeGreatness(int[] nums) {
3 // Sort the array in ascending order
4 Arrays.sort(nums);
5
6 // Initialize pointer for the smaller element in comparison
7 int smallerIndex = 0;
8
9 // Iterate through each element in the sorted array
10 for (int currentNum : nums) {
11 // If current element is greater than the element at smallerIndex,
12 // we found a valid pair where currentNum > nums[smallerIndex]
13 // This contributes to the greatness count
14 if (currentNum > nums[smallerIndex]) {
15 // Move the smaller pointer forward as we've used this element
16 smallerIndex++;
17 }
18 }
19
20 // The final position of smallerIndex represents the maximum greatness
21 // (number of indices where permutation value > original value)
22 return smallerIndex;
23 }
24}
25
1class Solution {
2public:
3 int maximizeGreatness(vector<int>& nums) {
4 // Sort the array in ascending order
5 sort(nums.begin(), nums.end());
6
7 // Use two-pointer technique:
8 // 'greatnessCount' tracks the position of smaller elements
9 // and also counts the number of valid pairs
10 int greatnessCount = 0;
11
12 // Iterate through each element in the sorted array
13 for (int currentNum : nums) {
14 // If current element is greater than the element at position 'greatnessCount',
15 // we found a valid pair where currentNum can be placed to achieve greatness
16 if (currentNum > nums[greatnessCount]) {
17 greatnessCount++;
18 }
19 }
20
21 return greatnessCount;
22 }
23};
24
1/**
2 * Finds the maximum greatness of the array.
3 * Greatness is defined as the maximum number of elements that can be made greater
4 * by rearranging the array such that nums2[i] > nums1[i].
5 *
6 * @param nums - The input array of numbers
7 * @returns The maximum greatness value
8 */
9function maximizeGreatness(nums: number[]): number {
10 // Sort the array in ascending order
11 nums.sort((a: number, b: number) => a - b);
12
13 // Pointer to track the position in the original sorted array
14 let pointerIndex: number = 0;
15
16 // Iterate through each element in the sorted array
17 for (const currentValue of nums) {
18 // If current value is greater than the value at pointer position,
19 // we can create a valid pair where currentValue > nums[pointerIndex]
20 if (currentValue > nums[pointerIndex]) {
21 // Move pointer forward as we've found a valid pairing
22 pointerIndex += 1;
23 }
24 }
25
26 // The pointer index represents the count of valid pairings (maximum greatness)
27 return pointerIndex;
28}
29
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 subsequent for loop iterates through the array once with O(n)
operations, but this is absorbed by the larger sorting complexity.
The space complexity is O(log n)
, where n
is the length of the array nums
. While the sorting is done in-place and doesn't require additional space proportional to the input size, the sorting algorithm itself uses O(log n)
space for the recursion stack during the divide-and-conquer process. The remaining operations use only O(1)
additional space for the variable i
and the loop variable x
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Pointer Logic
The Problem: Many developers initially misinterpret what the pointer i
represents and try to use two separate pointers or arrays to track matches. They might attempt something like:
def maximizeGreatness(self, nums: List[int]) -> int:
sorted_nums = sorted(nums)
original = sorted(nums) # Unnecessary copy
count = 0
used = [False] * len(nums) # Tracking used elements
for i in range(len(nums)):
for j in range(len(sorted_nums)):
if not used[j] and sorted_nums[j] > original[i]:
count += 1
used[j] = True
break
return count
Why It's Wrong: This approach overcomplicates the solution and doesn't leverage the key insight that we're comparing the same sorted array against itself with an offset.
The Fix: Understand that we're essentially trying to match elements from the sorted array with themselves, shifted by some positions. The single pointer tracks how many successful "shifts" we can make.
Pitfall 2: Confusing In-Place Modification
The Problem: Some might think the code is creating the actual permutation array and get confused when the function only returns a count:
def maximizeGreatness(self, nums: List[int]) -> int:
nums.sort()
perm = [] # Trying to build the actual permutation
i = 0
for x in nums:
if x > nums[i]:
perm.append(x) # Wrong approach
i += 1
return len(perm) # This won't give the correct answer
Why It's Wrong: The problem only asks for the maximum greatness value (a count), not the actual permutation. Building the permutation array incorrectly will lead to wrong results.
The Fix: Focus on counting successful pairings rather than constructing the actual permutation. The algorithm works by determining how many elements from the sorted array can be successfully paired with smaller elements.
Pitfall 3: Incorrect Boolean Addition
The Problem: In languages other than Python, or for developers unfamiliar with Python's boolean arithmetic, the line i += x > nums[i]
might be confusing or implemented incorrectly:
def maximizeGreatness(self, nums: List[int]) -> int:
nums.sort()
i = 0
for x in nums:
if x > nums[i]:
i = i + 1 # More verbose but clearer
# Forgetting the else case or incrementing i always
i += 1 # Wrong! This would increment twice when condition is true
return i
Why It's Wrong: Adding an extra increment outside the conditional would count elements twice or advance the pointer incorrectly.
The Fix: Use either the concise boolean addition i += x > nums[i]
or the explicit conditional:
for x in nums: if x > nums[i]: i += 1 # No else needed; i stays the same if condition is false
Pitfall 4: Not Handling Edge Cases
The Problem: Failing to consider arrays where all elements are identical:
nums = [1, 1, 1, 1]
Why It Matters: In this case, no element can be greater than any other, so the maximum greatness should be 0. The algorithm handles this correctly (the pointer never advances), but developers might overthink and add unnecessary special case handling.
The Fix: Trust the algorithm - it naturally handles this case without special logic since x > nums[i]
will always be False
when all elements are equal.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Donβt Miss This!