Facebook Pixel

870. Advantage Shuffle

Problem Description

You have two integer arrays nums1 and nums2 of equal length. The goal is to rearrange nums1 to maximize how many positions where nums1[i] > nums2[i].

The advantage is defined as the count of indices i where nums1[i] is greater than nums2[i]. You need to find a permutation (reordering) of nums1 that gives the maximum possible advantage over nums2.

For example, if nums1 = [2,7,11,15] and nums2 = [1,10,4,11], one optimal arrangement could be [2,11,7,15] which gives an advantage of 3 (at indices 0, 2, and 3 where 2>1, 7>4, and 15>11).

The solution uses a greedy strategy similar to the "Tian Ji's Horse Racing" problem. It sorts nums1 and creates a sorted list of (value, original_index) pairs from nums2. Then it uses two pointers to match elements:

  • If the smallest unused element from nums1 can beat the smallest unmatched element from nums2, use it there
  • Otherwise, "waste" it against the largest unmatched element from nums2 (since it can't win anyway, might as well save better elements for winnable positions)

This greedy approach ensures maximum advantage by efficiently using elements from nums1 - winning where possible and strategically losing where necessary.

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

Intuition

The key insight comes from thinking about this as an optimal matching problem. We want to use our resources (elements in nums1) as efficiently as possible to beat as many elements in nums2 as we can.

Think of it like a card game where you need to play your cards to beat your opponent's cards. If you have a card that's just barely strong enough to beat one of their cards, you should use it! But if your weakest card can't beat any of their remaining cards, you might as well "sacrifice" it against their strongest card to save your better cards for winnable matchups.

This leads us to a greedy strategy:

  1. Sort both arrays to understand the relative strengths
  2. Start with the smallest elements from both arrays
  3. If our smallest can beat their smallest, that's a win - use it!
  4. If our smallest can't beat their smallest, it probably can't beat anything else either, so "waste" it on their largest element

Why does this work? Consider what happens if we deviate from this strategy:

  • If we use a stronger element from nums1 where a weaker one would suffice, we're wasting potential - that stronger element might be needed elsewhere
  • If we don't sacrifice weak elements strategically, we might end up having to waste strong elements later

The two-pointer technique naturally implements this: pointer i tracks the smallest unmatched element in nums2 (sorted), pointer j tracks the largest. As we iterate through sorted nums1, we either score a point by beating element at i (and move i forward), or sacrifice against element at j (and move j backward).

This greedy approach guarantees we get the maximum possible advantage because we're always making the locally optimal choice that preserves our ability to win future matchups.

Learn more about Greedy, Two Pointers and Sorting patterns.

Solution Approach

The implementation follows a greedy algorithm with sorting and two-pointer technique:

Step 1: Sort nums1

nums1.sort()

We sort nums1 in ascending order to process elements from weakest to strongest.

Step 2: Create sorted pairs from nums2

t = sorted((v, i) for i, v in enumerate(nums2))

We create tuples of (value, original_index) from nums2 and sort them by value. This preserves the original positions while allowing us to work with sorted values. The variable t stores these sorted pairs.

Step 3: Initialize result array and pointers

n = len(nums2)
ans = [0] * n
i, j = 0, n - 1
  • ans: Result array to store the rearranged nums1 in original index positions of nums2
  • i: Points to the smallest unmatched element in sorted nums2
  • j: Points to the largest unmatched element in sorted nums2

Step 4: Greedy assignment

for v in nums1:
    if v <= t[i][0]:
        ans[t[j][1]] = v
        j -= 1
    else:
        ans[t[i][1]] = v
        i += 1

For each element v in sorted nums1:

  • If v <= t[i][0] (current element can't beat the smallest unmatched in nums2):
    • Assign v to the position of the largest unmatched element (t[j][1] gives original index)
    • Decrement j to move to next largest
  • Otherwise (current element can beat the smallest unmatched):
    • Assign v to the position of the smallest unmatched element (t[i][1] gives original index)
    • Increment i to move to next smallest

The algorithm ensures optimal advantage by:

  1. Using weak elements efficiently when they can win
  2. Sacrificing weak elements that can't win against strong opponents
  3. Preserving stronger elements for winnable matchups

Time Complexity: O(n log n) for sorting both arrays Space Complexity: O(n) for storing the sorted pairs and result array

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Input: nums1 = [12, 24, 8, 32], nums2 = [13, 25, 32, 11]

Step 1: Sort nums1

nums1 = [8, 12, 24, 32]  (sorted in ascending order)

Step 2: Create sorted pairs from nums2

Original nums2: [13, 25, 32, 11]
With indices:   [(13,0), (25,1), (32,2), (11,3)]
Sorted by value: [(11,3), (13,0), (25,1), (32,2)]
t = [(11,3), (13,0), (25,1), (32,2)]

Step 3: Initialize variables

ans = [0, 0, 0, 0]  (result array)
i = 0  (points to (11,3))
j = 3  (points to (32,2))

Step 4: Process each element from sorted nums1

Processing v = 8:

  • Compare: 8 <= 11? Yes
  • Can't beat smallest (11), so sacrifice against largest (32)
  • Place 8 at index 2: ans = [0, 0, 8, 0]
  • Move j from 3 to 2

Processing v = 12:

  • Compare: 12 <= 11? No
  • Can beat smallest (11)!
  • Place 12 at index 3: ans = [0, 0, 8, 12]
  • Move i from 0 to 1

Processing v = 24:

  • Compare: 24 <= 13? No
  • Can beat next smallest (13)!
  • Place 24 at index 0: ans = [24, 0, 8, 12]
  • Move i from 1 to 2

Processing v = 32:

  • Compare: 32 <= 25? No
  • Can beat next smallest (25)!
  • Place 32 at index 1: ans = [24, 32, 8, 12]

Final Result:

  • Rearranged nums1: [24, 32, 8, 12]
  • Original nums2: [13, 25, 32, 11]
  • Comparisons: 24>13 βœ“, 32>25 βœ“, 8<32 βœ—, 12>11 βœ“
  • Advantage = 3

The greedy strategy efficiently used the weakest element (8) against an unbeatable opponent (32), while saving stronger elements (12, 24, 32) for winnable matchups against (11, 13, 25).

Solution Implementation

1class Solution:
2    def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:
3        # Sort nums1 in ascending order for greedy matching
4        nums1.sort()
5      
6        # Create tuples of (value, original_index) from nums2 and sort by value
7        # This allows us to track original positions while working with sorted values
8        sorted_nums2_with_indices = sorted((value, index) for index, value in enumerate(nums2))
9      
10        # Get the length of the arrays
11        n = len(nums2)
12      
13        # Initialize result array with zeros
14        result = [0] * n
15      
16        # Two pointers: left for smallest unmatched in nums2, right for largest unmatched
17        left_pointer = 0
18        right_pointer = n - 1
19      
20        # Greedy strategy: iterate through sorted nums1
21        for num in nums1:
22            # If current number from nums1 cannot beat the smallest unmatched from nums2
23            if num <= sorted_nums2_with_indices[left_pointer][0]:
24                # Use this number against the largest unmatched element (sacrifice strategy)
25                original_index = sorted_nums2_with_indices[right_pointer][1]
26                result[original_index] = num
27                right_pointer -= 1
28            else:
29                # Current number can beat the smallest unmatched, so match them
30                original_index = sorted_nums2_with_indices[left_pointer][1]
31                result[original_index] = num
32                left_pointer += 1
33      
34        return result
35
1class Solution {
2    public int[] advantageCount(int[] nums1, int[] nums2) {
3        int n = nums1.length;
4      
5        // Create a 2D array to store nums2 values with their original indices
6        // Each element is [value, original_index]
7        int[][] nums2WithIndex = new int[n][2];
8        for (int i = 0; i < n; ++i) {
9            nums2WithIndex[i] = new int[] {nums2[i], i};
10        }
11      
12        // Sort nums2WithIndex by value in ascending order
13        Arrays.sort(nums2WithIndex, (a, b) -> a[0] - b[0]);
14      
15        // Sort nums1 in ascending order
16        Arrays.sort(nums1);
17      
18        // Initialize result array
19        int[] result = new int[n];
20      
21        // Two pointers for nums2WithIndex
22        // left: points to smallest unmatched element in nums2
23        // right: points to largest unmatched element in nums2
24        int left = 0;
25        int right = n - 1;
26      
27        // Greedy approach: iterate through sorted nums1
28        for (int currentNum : nums1) {
29            if (currentNum <= nums2WithIndex[left][0]) {
30                // Current number from nums1 cannot beat the smallest unmatched in nums2
31                // Use it against the largest unmatched element (waste it strategically)
32                int originalIndex = nums2WithIndex[right][1];
33                result[originalIndex] = currentNum;
34                right--;
35            } else {
36                // Current number from nums1 can beat the smallest unmatched in nums2
37                // Use it to gain advantage
38                int originalIndex = nums2WithIndex[left][1];
39                result[originalIndex] = currentNum;
40                left++;
41            }
42        }
43      
44        return result;
45    }
46}
47
1class Solution {
2public:
3    vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
4        int n = nums1.size();
5      
6        // Create pairs of (value, original_index) for nums2 to track positions
7        vector<pair<int, int>> nums2WithIndex;
8        for (int i = 0; i < n; ++i) {
9            nums2WithIndex.push_back({nums2[i], i});
10        }
11      
12        // Sort both arrays to apply greedy strategy
13        sort(nums2WithIndex.begin(), nums2WithIndex.end());
14        sort(nums1.begin(), nums1.end());
15      
16        // Two pointers for nums2WithIndex
17        int left = 0;   // Points to smallest unmatched element in sorted nums2
18        int right = n - 1;  // Points to largest unmatched element in sorted nums2
19      
20        vector<int> result(n);
21      
22        // Greedy assignment: for each element in sorted nums1
23        for (int value : nums1) {
24            if (value <= nums2WithIndex[left].first) {
25                // Current value can't beat smallest unmatched in nums2
26                // Assign it to the largest unmatched element (waste it)
27                result[nums2WithIndex[right].second] = value;
28                right--;
29            } else {
30                // Current value can beat smallest unmatched in nums2
31                // Make the advantageous assignment
32                result[nums2WithIndex[left].second] = value;
33                left++;
34            }
35        }
36      
37        return result;
38    }
39};
40
1/**
2 * Rearranges nums1 to maximize its advantage over nums2 (Leetcode 870: Advantage Shuffle)
3 * Uses a greedy approach: assign smallest values from nums1 that can beat nums2 elements,
4 * otherwise assign them to positions that are already lost
5 * 
6 * @param nums1 - First array to be rearranged
7 * @param nums2 - Second array to compare against
8 * @returns Rearranged nums1 array that maximizes advantage over nums2
9 */
10function advantageCount(nums1: number[], nums2: number[]): number[] {
11    const arrayLength: number = nums1.length;
12  
13    // Create an array of indices [0, 1, 2, ..., n-1]
14    const sortedIndices: number[] = Array.from({ length: arrayLength }, (_, index) => index);
15  
16    // Sort indices based on corresponding values in nums2 (ascending order)
17    sortedIndices.sort((indexA: number, indexB: number) => nums2[indexA] - nums2[indexB]);
18  
19    // Sort nums1 in ascending order
20    nums1.sort((a: number, b: number) => a - b);
21
22    // Initialize result array with zeros
23    const result: number[] = new Array(arrayLength).fill(0);
24  
25    // Two pointers for tracking positions in sortedIndices
26    let leftPointer: number = 0;  // Points to smallest unassigned element in nums2
27    let rightPointer: number = arrayLength - 1;  // Points to largest unassigned element in nums2
28  
29    // Iterate through sorted nums1 and assign each element optimally
30    for (let i = 0; i < arrayLength; i++) {
31        // If current nums1 element can beat the smallest unassigned nums2 element
32        if (nums1[i] > nums2[sortedIndices[leftPointer]]) {
33            // Assign it to beat that position
34            result[sortedIndices[leftPointer]] = nums1[i];
35            leftPointer++;
36        } else {
37            // Otherwise, assign it to the largest unassigned position (already lost)
38            result[sortedIndices[rightPointer]] = nums1[i];
39            rightPointer--;
40        }
41    }
42  
43    return result;
44}
45

Time and Space Complexity

Time Complexity: O(n log n) where n is the length of the input arrays.

  • Sorting nums1 takes O(n log n) time
  • Creating the sorted list t from nums2 with indices takes O(n log n) time (creating the tuples is O(n) and sorting them is O(n log n))
  • The for loop iterates through all elements in nums1 once, performing constant time operations in each iteration, which takes O(n) time
  • Overall: O(n log n) + O(n log n) + O(n) = O(n log n)

Space Complexity: O(n) where n is the length of the input arrays.

  • The sorted list t stores n tuples, each containing a value and index, requiring O(n) space
  • The answer array ans stores n elements, requiring O(n) space
  • The sorting operation on nums1 is done in-place (for Python's Timsort, the worst-case space is O(n))
  • Variables i, j, and v use constant O(1) space
  • Overall: O(n) + O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Pointer Movement Logic

A common mistake is confusing when to move which pointer. Developers often incorrectly move the left pointer when sacrificing an element or move the right pointer when winning a matchup.

Incorrect Implementation:

for num in nums1:
    if num <= sorted_nums2_with_indices[left_pointer][0]:
        # Wrong: moving left pointer when sacrificing
        result[sorted_nums2_with_indices[left_pointer][1]] = num
        left_pointer += 1  # This is wrong!
    else:
        # Wrong: using right pointer when winning
        result[sorted_nums2_with_indices[right_pointer][1]] = num
        right_pointer -= 1  # This is wrong!

Why it's wrong: When we can't win (num <= smallest unmatched), we should sacrifice against the largest unmatched element to save potential wins for later. The logic should be inverted.

Correct Approach:

for num in nums1:
    if num <= sorted_nums2_with_indices[left_pointer][0]:
        # Sacrifice against largest unmatched
        result[sorted_nums2_with_indices[right_pointer][1]] = num
        right_pointer -= 1
    else:
        # Win against smallest unmatched
        result[sorted_nums2_with_indices[left_pointer][1]] = num
        left_pointer += 1

2. Not Preserving Original Indices

Another pitfall is forgetting to track the original indices of nums2 when sorting, leading to incorrect placement in the result array.

Incorrect Implementation:

nums1.sort()
nums2_sorted = sorted(nums2)  # Lost original indices!
result = []

# Now we have no way to know where each element should go
for num in nums1:
    # Can't map back to original positions
    ...

Correct Approach:

# Preserve original indices using enumerate
sorted_nums2_with_indices = sorted((value, index) for index, value in enumerate(nums2))
# Now sorted_nums2_with_indices[i][1] gives us the original index

3. Off-by-One Errors with Comparison

Using < instead of <= in the comparison can lead to suboptimal solutions.

Incorrect Implementation:

if num < sorted_nums2_with_indices[left_pointer][0]:  # Using < instead of <=
    # Sacrifice
    ...

Why it matters: When nums1[i] == nums2[j], there's no advantage gained. These equal cases should be treated as losses and handled by the sacrifice strategy. Using < would incorrectly treat equal values as wins, potentially wasting elements that could win elsewhere.

4. Modifying Input Arrays Without Consideration

If the problem requires preserving the original arrays or if they're used elsewhere, modifying nums1 directly with nums1.sort() could cause issues.

Safe Approach:

def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]:
    # Create a copy if original needs to be preserved
    sorted_nums1 = sorted(nums1)  # Creates new sorted list
    # ... rest of the logic
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used in a depth first search?


Recommended Readings

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

Load More