3774. Absolute Difference Between Maximum and Minimum K Elements

Difficulty: Easy
Topics: Array, Sorting, Heap, Quickselect
Companies: Amazon, Google, Microsoft
Acceptance Rate: 74.8%
Absolute Difference K Elements visualization
Pro Tip: For this problem with n ≤ 100, simple sorting (O(n log n)) is perfectly adequate and the most readable solution. However, understanding heap-based (O(n log k)) and selection-based (O(n)) approaches is valuable for interview preparation where constraints might be larger.

Problem Statement

You are given an integer array nums and an integer k.

Find the absolute difference between:

Return an integer denoting this difference.

Example 1

Input: nums = [5,2,2,4], k = 2

Output: 5

Explanation:

  • The k = 2 largest elements are 4 and 5. Their sum is 4 + 5 = 9.
  • The k = 2 smallest elements are 2 and 2. Their sum is 2 + 2 = 4.
  • The absolute difference is abs(9 - 4) = 5.

Example 2

Input: nums = [100], k = 1

Output: 0

Explanation:

  • The largest element is 100.
  • The smallest element is 100.
  • The absolute difference is abs(100 - 100) = 0.

Visualization

For nums = [5,2,2,4], k = 2:

2
2
4
5

Smallest k = 2: 2 + 2 = 4

Largest k = 2: 4 + 5 = 9

Difference: |9 - 4| = 5

Problem Link: View on LeetCode ↗

Key Insight

The solution requires finding two distinct sums from the same array:

  1. Sum of k smallest elements: The k elements with minimum values
  2. Sum of k largest elements: The k elements with maximum values

Important observations:

Important: When the array has duplicate values, the same value might appear in both k smallest and k largest groups if there aren't enough distinct values. For example, in [1,1,2,3] with k=2, the k smallest are [1,1] and k largest are [2,3] - no overlap of indices but value 1 appears twice in smallest.

Approach 1: Sorting and Direct Summation (Optimal for Given Constraints)

Sort the array and directly sum the first k elements (smallest) and last k elements (largest), then compute their absolute difference.

Algorithm Steps

  1. Sort the array in non-decreasing order
  2. Calculate sum of first k elements (smallest)
  3. Calculate sum of last k elements (largest)
  4. Return absolute difference between the two sums

How It Works

After sorting, the smallest k elements are at indices 0 to k-1, and the largest k elements are at indices n-k to n-1. We sum these ranges directly.

Complexity Analysis

Time Complexity Space Complexity
O(n log n) O(1) or O(log n) for sorting algorithm

Where n is the length of nums array. The dominant factor is sorting. Built-in sort typically uses O(log n) space for recursion or O(1) for iterative sorts.

Sorting Approach (Your Solution)
class Solution {
public:
    int absDifference(vector<int>& nums, int k) {
        int n = nums.size();
        
        // Sort the array in non-decreasing order
        sort(begin(nums), end(nums));
        
        // Calculate sum of k smallest elements (first k)
        int sumSmallest = accumulate(begin(nums), begin(nums) + k, 0);
        
        // Calculate sum of k largest elements (last k)
        int sumLargest = accumulate(end(nums) - k, end(nums), 0);
        
        // Return absolute difference
        return abs(sumLargest - sumSmallest);
    }
};
import java.util.*;

class Solution {
    public int absDifference(int[] nums, int k) {
        int n = nums.length;
        
        // Sort the array in non-decreasing order
        Arrays.sort(nums);
        
        // Calculate sum of k smallest elements (first k)
        int sumSmallest = 0;
        for (int i = 0; i < k; i++) {
            sumSmallest += nums[i];
        }
        
        // Calculate sum of k largest elements (last k)
        int sumLargest = 0;
        for (int i = n - k; i < n; i++) {
            sumLargest += nums[i];
        }
        
        // Return absolute difference
        return Math.abs(sumLargest - sumSmallest);
    }
}
class Solution:
    def absDifference(self, nums: list[int], k: int) -> int:
        n = len(nums)
        
        # Sort the array in non-decreasing order
        nums.sort()
        
        # Calculate sum of k smallest elements (first k)
        sum_smallest = sum(nums[:k])
        
        # Calculate sum of k largest elements (last k)
        sum_largest = sum(nums[-k:])
        
        # Return absolute difference
        return abs(sum_largest - sum_smallest)
Optimal for Constraints: With n ≤ 100, O(n log n) sorting is extremely efficient and provides the cleanest, most readable solution. This approach works correctly even with duplicate elements.

Approach 2: Heap-Based Selection (O(n log k) Time)

Use a max-heap to find k smallest elements and a min-heap to find k largest elements without fully sorting the array.

Algorithm Steps

  1. For k smallest elements:
    • Use a max-heap (priority queue) of size k
    • Add each element, removing largest when heap size > k
    • Remaining heap contains k smallest elements
  2. For k largest elements:
    • Use a min-heap (priority queue) of size k
    • Add each element, removing smallest when heap size > k
    • Remaining heap contains k largest elements
  3. Sum elements in both heaps and compute absolute difference

How It Works

A max-heap keeps track of the largest among the smallest k elements seen so far. When we see a smaller element, it replaces the current largest in the heap. Similarly, a min-heap keeps track of the smallest among the largest k elements.

Complexity Analysis

Time Complexity Space Complexity
O(n log k) O(k)

We process n elements, performing O(log k) heap operations for each. Space is O(k) for storing the heaps.

Heap-Based Approach
class Solution {
public:
    int absDifference(vector<int>& nums, int k) {
        // Max-heap for k smallest elements (we want to keep smallest, so remove largest)
        priority_queue<int> maxHeap; // default is max-heap
        
        // Min-heap for k largest elements (we want to keep largest, so remove smallest)
        priority_queue<int, vector<int>, greater<int>> minHeap;
        
        for (int num : nums) {
            // Process for k smallest (max-heap)
            maxHeap.push(num);
            if (maxHeap.size() > k) {
                maxHeap.pop(); // Remove the largest (root of max-heap)
            }
            
            // Process for k largest (min-heap)
            minHeap.push(num);
            if (minHeap.size() > k) {
                minHeap.pop(); // Remove the smallest (root of min-heap)
            }
        }
        
        // Calculate sum of k smallest elements from max-heap
        int sumSmallest = 0;
        while (!maxHeap.empty()) {
            sumSmallest += maxHeap.top();
            maxHeap.pop();
        }
        
        // Calculate sum of k largest elements from min-heap
        int sumLargest = 0;
        while (!minHeap.empty()) {
            sumLargest += minHeap.top();
            minHeap.pop();
        }
        
        return abs(sumLargest - sumSmallest);
    }
};
import java.util.*;

class Solution {
    public int absDifference(int[] nums, int k) {
        // Max-heap for k smallest elements (we want to keep smallest, so remove largest)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
        
        // Min-heap for k largest elements (we want to keep largest, so remove smallest)
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        
        for (int num : nums) {
            // Process for k smallest (max-heap)
            maxHeap.offer(num);
            if (maxHeap.size() > k) {
                maxHeap.poll(); // Remove the largest (root of max-heap)
            }
            
            // Process for k largest (min-heap)
            minHeap.offer(num);
            if (minHeap.size() > k) {
                minHeap.poll(); // Remove the smallest (root of min-heap)
            }
        }
        
        // Calculate sum of k smallest elements from max-heap
        int sumSmallest = 0;
        while (!maxHeap.isEmpty()) {
            sumSmallest += maxHeap.poll();
        }
        
        // Calculate sum of k largest elements from min-heap
        int sumLargest = 0;
        while (!minHeap.isEmpty()) {
            sumLargest += minHeap.poll();
        }
        
        return Math.abs(sumLargest - sumSmallest);
    }
}
import heapq

class Solution:
    def absDifference(self, nums: list[int], k: int) -> int:
        # Max-heap for k smallest elements (we use min-heap with negative values)
        max_heap = []  # Will store negatives to simulate max-heap
        
        # Min-heap for k largest elements
        min_heap = []
        
        for num in nums:
            # Process for k smallest (using negative to simulate max-heap)
            heapq.heappush(max_heap, -num)
            if len(max_heap) > k:
                heapq.heappop(max_heap)  # Remove the largest (most negative when negated)
            
            # Process for k largest (standard min-heap)
            heapq.heappush(min_heap, num)
            if len(min_heap) > k:
                heapq.heappop(min_heap)  # Remove the smallest
        
        # Calculate sum of k smallest elements (negate back from max-heap)
        sum_smallest = sum(-x for x in max_heap)
        
        # Calculate sum of k largest elements from min-heap
        sum_largest = sum(min_heap)
        
        return abs(sum_largest - sum_smallest)
Heap Insight: This approach is more efficient than sorting when k is much smaller than n. For k approaching n, it becomes O(n log n) similar to sorting, but for small k, it's O(n log k) which is better than O(n log n).

Approach 3: Quickselect Algorithm (O(n) Average Time)

Use the Quickselect algorithm to find the k smallest and k largest elements without fully sorting the array.

Algorithm Steps

  1. Make a copy of the array to avoid modifying the original
  2. Use Quickselect to find the kth smallest element and partition the array
  3. Sum all elements ≤ kth smallest (handling duplicates carefully)
  4. Similarly find kth largest and sum all elements ≥ kth largest
  5. Compute absolute difference

How It Works

Quickselect is a selection algorithm to find the kth smallest/largest element. It uses the same partitioning scheme as Quicksort but only recurses into one side.

Complexity Analysis

Time Complexity Space Complexity
O(n) average, O(n²) worst-case O(1) or O(log n) for recursion

Average case is linear time. Worst-case is quadratic but can be avoided with randomized pivot selection.

Quickselect Approach
class Solution {
public:
    
    // Helper function for Quickselect
    int partition(vector<int>& nums, int left, int right, int pivotIndex) {
        int pivotValue = nums[pivotIndex];
        swap(nums[pivotIndex], nums[right]); // Move pivot to end
        int storeIndex = left;
        
        for (int i = left; i < right; i++) {
            if (nums[i] < pivotValue) {
                swap(nums[storeIndex], nums[i]);
                storeIndex++;
            }
        }
        swap(nums[right], nums[storeIndex]); // Move pivot to its final place
        return storeIndex;
    }
    
    // Quickselect to find kth smallest element
    int quickselect(vector<int>& nums, int left, int right, int k) {
        if (left == right) return nums[left];
        
        // Random pivot for better average performance
        int pivotIndex = left + rand() % (right - left + 1);
        pivotIndex = partition(nums, left, right, pivotIndex);
        
        if (k == pivotIndex) {
            return nums[k];
        } else if (k < pivotIndex) {
            return quickselect(nums, left, pivotIndex - 1, k);
        } else {
            return quickselect(nums, pivotIndex + 1, right, k);
        }
    }
    
    int absDifference(vector<int>& nums, int k) {
        int n = nums.size();
        
        // Make copies for finding smallest and largest
        vector<int> numsForSmallest = nums;
        vector<int> numsForLargest = nums;
        
        // Find kth smallest element
        int kthSmallest = quickselect(numsForSmallest, 0, n - 1, k - 1);
        
        // Find kth largest element (which is (n-k)th smallest)
        int kthLargest = quickselect(numsForLargest, 0, n - 1, n - k);
        
        // Sum k smallest elements
        int sumSmallest = 0;
        for (int num : nums) {
            if (num < kthSmallest) {
                sumSmallest += num;
            }
        }
        
        // Count how many elements equal to kthSmallest we need to include
        int countKthSmallest = 0;
        for (int num : nums) {
            if (num == kthSmallest) countKthSmallest++;
        }
        
        // Add remaining elements equal to kthSmallest to reach k total
        int needed = k;
        for (int num : nums) {
            if (num < kthSmallest) needed--;
        }
        sumSmallest += needed * kthSmallest;
        
        // Sum k largest elements
        int sumLargest = 0;
        for (int num : nums) {
            if (num > kthLargest) {
                sumLargest += num;
            }
        }
        
        // Count how many elements equal to kthLargest we need to include
        int countKthLargest = 0;
        for (int num : nums) {
            if (num == kthLargest) countKthLargest++;
        }
        
        // Add remaining elements equal to kthLargest to reach k total
        needed = k;
        for (int num : nums) {
            if (num > kthLargest) needed--;
        }
        sumLargest += needed * kthLargest;
        
        return abs(sumLargest - sumSmallest);
    }
};
import java.util.*;

class Solution {
    
    private int partition(int[] nums, int left, int right, int pivotIndex) {
        int pivotValue = nums[pivotIndex];
        // Move pivot to end
        int temp = nums[pivotIndex];
        nums[pivotIndex] = nums[right];
        nums[right] = temp;
        
        int storeIndex = left;
        
        for (int i = left; i < right; i++) {
            if (nums[i] < pivotValue) {
                temp = nums[storeIndex];
                nums[storeIndex] = nums[i];
                nums[i] = temp;
                storeIndex++;
            }
        }
        
        // Move pivot to its final place
        temp = nums[right];
        nums[right] = nums[storeIndex];
        nums[storeIndex] = temp;
        
        return storeIndex;
    }
    
    private int quickselect(int[] nums, int left, int right, int k) {
        if (left == right) return nums[left];
        
        Random random = new Random();
        int pivotIndex = left + random.nextInt(right - left + 1);
        pivotIndex = partition(nums, left, right, pivotIndex);
        
        if (k == pivotIndex) {
            return nums[k];
        } else if (k < pivotIndex) {
            return quickselect(nums, left, pivotIndex - 1, k);
        } else {
            return quickselect(nums, pivotIndex + 1, right, k);
        }
    }
    
    public int absDifference(int[] nums, int k) {
        int n = nums.length;
        
        // Make copies for finding smallest and largest
        int[] numsForSmallest = nums.clone();
        int[] numsForLargest = nums.clone();
        
        // Find kth smallest element
        int kthSmallest = quickselect(numsForSmallest, 0, n - 1, k - 1);
        
        // Find kth largest element (which is (n-k)th smallest)
        int kthLargest = quickselect(numsForLargest, 0, n - 1, n - k);
        
        // Sum k smallest elements
        int sumSmallest = 0;
        for (int num : nums) {
            if (num < kthSmallest) {
                sumSmallest += num;
            }
        }
        
        // Count how many elements equal to kthSmallest we need to include
        int countKthSmallest = 0;
        for (int num : nums) {
            if (num == kthSmallest) countKthSmallest++;
        }
        
        // Add remaining elements equal to kthSmallest to reach k total
        int needed = k;
        for (int num : nums) {
            if (num < kthSmallest) needed--;
        }
        sumSmallest += needed * kthSmallest;
        
        // Sum k largest elements
        int sumLargest = 0;
        for (int num : nums) {
            if (num > kthLargest) {
                sumLargest += num;
            }
        }
        
        // Count how many elements equal to kthLargest we need to include
        int countKthLargest = 0;
        for (int num : nums) {
            if (num == kthLargest) countKthLargest++;
        }
        
        // Add remaining elements equal to kthLargest to reach k total
        needed = k;
        for (int num : nums) {
            if (num > kthLargest) needed--;
        }
        sumLargest += needed * kthLargest;
        
        return Math.abs(sumLargest - sumSmallest);
    }
}
import random

class Solution:
    
    def partition(self, nums: list[int], left: int, right: int, pivot_index: int) -> int:
        pivot_value = nums[pivot_index]
        # Move pivot to end
        nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
        
        store_index = left
        
        for i in range(left, right):
            if nums[i] < pivot_value:
                nums[store_index], nums[i] = nums[i], nums[store_index]
                store_index += 1
        
        # Move pivot to its final place
        nums[right], nums[store_index] = nums[store_index], nums[right]
        
        return store_index
    
    def quickselect(self, nums: list[int], left: int, right: int, k: int) -> int:
        if left == right:
            return nums[left]
        
        # Random pivot for better average performance
        pivot_index = random.randint(left, right)
        pivot_index = self.partition(nums, left, right, pivot_index)
        
        if k == pivot_index:
            return nums[k]
        elif k < pivot_index:
            return self.quickselect(nums, left, pivot_index - 1, k)
        else:
            return self.quickselect(nums, pivot_index + 1, right, k)
    
    def absDifference(self, nums: list[int], k: int) -> int:
        n = len(nums)
        
        # Make copies for finding smallest and largest
        nums_for_smallest = nums.copy()
        nums_for_largest = nums.copy()
        
        # Find kth smallest element
        kth_smallest = self.quickselect(nums_for_smallest, 0, n - 1, k - 1)
        
        # Find kth largest element (which is (n-k)th smallest)
        kth_largest = self.quickselect(nums_for_largest, 0, n - 1, n - k)
        
        # Sum k smallest elements
        sum_smallest = 0
        for num in nums:
            if num < kth_smallest:
                sum_smallest += num
        
        # Count how many elements equal to kth_smallest we need to include
        count_kth_smallest = nums.count(kth_smallest)
        
        # Add remaining elements equal to kth_smallest to reach k total
        needed = k
        for num in nums:
            if num < kth_smallest:
                needed -= 1
        sum_smallest += needed * kth_smallest
        
        # Sum k largest elements
        sum_largest = 0
        for num in nums:
            if num > kth_largest:
                sum_largest += num
        
        # Count how many elements equal to kth_largest we need to include
        count_kth_largest = nums.count(kth_largest)
        
        # Add remaining elements equal to kth_largest to reach k total
        needed = k
        for num in nums:
            if num > kth_largest:
                needed -= 1
        sum_largest += needed * kth_largest
        
        return abs(sum_largest - sum_smallest)
Quickselect Insight: This approach provides average O(n) time complexity, which is theoretically optimal for selection problems. However, it's more complex to implement correctly, especially when dealing with duplicate elements and ensuring we get exactly k elements in our sums.

Comparison of Approaches

Approach Time Complexity Space Complexity Key Insight Best Use Case
Sorting O(n log n) O(1) or O(log n) Simplest, most readable Small n (≤100), interviews when readability matters
Heap-Based O(n log k) O(k) Efficient when k ≪ n Large n, small k, streaming data
Quickselect O(n) avg, O(n²) worst O(1) or O(log n) Theoretically optimal average case Large n, performance-critical applications
Recommendation: For LeetCode 3774 with n ≤ 100, the sorting approach is perfectly adequate, easiest to implement, and least error-prone. The heap-based approach is good practice for interview problems with larger constraints, and quickselect is valuable knowledge for algorithm enthusiasts.

Edge Cases and Testing

1. k = n (All Elements)

Input: nums = [1,2,3,4], k = 4 → Output: 0
Sum of k smallest = 1+2+3+4 = 10
Sum of k largest = 1+2+3+4 = 10
Difference = |10-10| = 0

2. k = 1 (Single Element)

Input: nums = [5,2,8,1], k = 1 → Output: 7
Smallest = 1, Largest = 8
Difference = |8-1| = 7

3. Duplicate Elements

Input: nums = [2,2,2,2], k = 2 → Output: 0
Smallest 2 elements: 2+2 = 4
Largest 2 elements: 2+2 = 4
Difference = |4-4| = 0

4. Array with Negative Numbers

Note: Problem constraints say nums[i] ≥ 1, but if negatives were allowed:

Input: nums = [-5, -2, 0, 3, 7], k = 2 → Output: 12
Smallest: -5 + -2 = -7
Largest: 3 + 7 = 10
Difference = |10 - (-7)| = 17

5. k Larger Than Distinct Values

Input: nums = [1,1,2,3,3], k = 4 → Output: 5
Smallest 4: 1+1+2+3 = 7
Largest 4: 1+2+3+3 = 9
Difference = |9-7| = 2
Important: When k = n/2 and n is even, the middle elements might be counted in both smallest and largest if there are duplicates. The problem allows this as it's about values, not indices.

FAQs

1. Why is the sorting approach optimal for this problem's constraints?

With n ≤ 100, O(n log n) sorting (approximately 100 * log₂(100) ≈ 700 operations) is extremely fast and simple to implement. More complex algorithms like quickselect have higher constant factors and implementation complexity that aren't justified for such small inputs.

2. What if the same element appears in both k smallest and k largest?

That's allowed by the problem statement. The problem asks for "the sum of the k largest elements" and "the sum of the k smallest elements" based on values, not indices. If an element value appears multiple times, it can contribute to both sums.

3. Can we use counting sort given nums[i] ≤ 100?

Yes! With values limited to 1-100, counting sort with O(n + range) = O(n + 100) = O(n) is possible. Create a frequency array of size 101, fill it, then iterate to find k smallest and largest sums.

4. What's the difference between using a max-heap vs min-heap for k smallest?

For k smallest elements, we want to keep the k smallest values seen so far. A max-heap lets us quickly remove the largest of those k when we find a smaller element. For k largest elements, a min-heap lets us quickly remove the smallest of those k when we find a larger element.

5. How does quickselect handle duplicate elements?

The standard quickselect algorithm as shown partitions elements into "less than pivot" and "greater than or equal to pivot". With duplicates, all elements equal to the pivot end up together after partitioning. We need additional logic to count how many of the pivot value to include in our sum.

6. Is there a Python one-liner solution?

Yes! Python's expressive syntax allows: return abs(sum(sorted(nums)[-k:]) - sum(sorted(nums)[:k])). This is readable and efficient for the given constraints.

7. What happens when k = 0?

The problem constraints state 1 ≤ k ≤ n, so k = 0 is not a valid input. If it were allowed, both sums would be 0, so the difference would be 0.

8. Can we solve this without modifying the input array?

Yes, by making a copy before sorting or using heap/quickselect approaches that don't require in-place modification of the original array. The sorting approach shown modifies the input; to avoid this, create a copy first.

9. What's the memory usage of each approach?

Sorting: O(1) or O(log n) for recursion stack. Heap: O(k) for storing heap elements. Quickselect: O(1) or O(log n) for recursion stack (can be implemented iteratively to use O(1)).

10. How would you modify the solution for very large n (e.g., 10⁷)?

For extremely large n that doesn't fit in memory, use:
1. Streaming algorithm with two heaps of size k (O(n log k) time, O(k) space)
2. If k is also large, use approximate algorithms or distributed computing
3. If data is on disk, use external sorting

About Algopush: Algopush provides comprehensive algorithm solutions and explanations for coding interview preparation. Visit algopush.com for more problem solutions and coding resources.