Partition Array Such That Maximum Difference Is K
MedPrompt
You are given an integer array nums and an integer k. You may partition nums into one or more subsequences such that each element in nums appears in exactly one of the subsequences.
Return the minimum number of subsequences needed such that the difference between the maximum and minimum values in each subsequence is at most k.
A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements.
Example 1:
Input: nums = [3,6,1,2,5], k = 2
Output: 2
Explanation:
We can partition nums into the two subsequences [3,1,2] and [6,5].
The difference between the maximum and minimum value in the first subsequence is 3 - 1 = 2.
The difference between the maximum and minimum value in the second subsequence is 6 - 5 = 1.
Since two subsequences were created, we return 2. It can be shown that 2 is the minimum number of subsequences needed.Example 2:
Input: nums = [1,2,3], k = 1
Output: 2
Explanation:
We can partition nums into the two subsequences [1,2] and [3].
The difference between the maximum and minimum value in the first subsequence is 2 - 1 = 1.
The difference between the maximum and minimum value in the second subsequence is 3 - 3 = 0.
Since two subsequences were created, we return 2. Note that another optimal solution is to partition nums into the two subsequences [1] and [2,3].Example 3:
Input: nums = [2,2,4,5], k = 0
Output: 3
Explanation:
We can partition nums into the three subsequences [2,2], [4], and [5].
The difference between the maximum and minimum value in the first subsequences is 2 - 2 = 0.
The difference between the maximum and minimum value in the second subsequences is 4 - 4 = 0.
The difference between the maximum and minimum value in the third subsequences is 5 - 5 = 0.
Since three subsequences were created, we return 3. It can be shown that 3 is the minimum number of subsequences needed.
Constraints:
1 <= nums.length <= 1050 <= nums[i] <= 1050 <= k <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach is based on the key observation that to minimize the number of subsequences, we should make each subsequence as "full" as possible. Since the problem deals with subsequences, the original order of elements does not matter. This allows us to sort the array first. After sorting, we can greedily form subsequences. We start a new subsequence with the smallest available element and include all subsequent elements that fit within the k difference constraint.
Algorithm
- If the input array
numsis empty, return 0. - Sort the array
numsin non-decreasing order. - Initialize a counter for the number of subsequences,
partitions, to 1. - Set the minimum value of the current subsequence,
minVal, to the first element of the sorted array,nums[0]. - Iterate through the sorted array starting from the second element (
i = 1ton-1):- For each element
nums[i], check if the differencenums[i] - minValis greater thank. - If it is, the current element
nums[i]cannot be part of the current subsequence. We must start a new one. - To do this, increment
partitionsand updateminValtonums[i].
- For each element
- After the loop finishes, return the total
partitionscount.
Walkthrough
The core idea is that if we sort the array nums, we can process the elements in increasing order. This simplifies the problem of finding the minimum and maximum within a potential subsequence.
We initialize the count of subsequences to 1 and take the first element of the sorted array, nums[0], as the minimum value (minVal) for our first subsequence.
We then iterate through the rest of the sorted array. Any element nums[i] can be added to the current subsequence as long as nums[i] - minVal <= k. Since the array is sorted, we are guaranteed that nums[i] will be the maximum element considered so far for the current subsequence, and minVal will remain the minimum.
When we encounter an element nums[j] such that nums[j] - minVal > k, we know that nums[j] (and all elements after it, due to the sorted order) cannot belong to the current subsequence. At this point, we must finalize the current subsequence and start a new one. We do this by incrementing our subsequence counter and setting nums[j] as the minVal for this new subsequence.
This greedy process is repeated until all elements in the sorted array have been placed into a subsequence. The final count is guaranteed to be the minimum number of subsequences required.
import java.util.Arrays; class Solution { public int partitionArray(int[] nums, int k) { if (nums == null || nums.length == 0) { return 0; } Arrays.sort(nums); int partitions = 1; int minVal = nums[0]; for (int i = 1; i < nums.length; i++) { if (nums[i] - minVal > k) { partitions++; minVal = nums[i]; } } return partitions; }}Complexity
Time
O(N log N), where N is the number of elements in `nums`. The `Arrays.sort()` method takes O(N log N) time. The subsequent single pass through the array takes O(N) time. Thus, the total time complexity is dominated by the sorting step.
Space
O(log N) or O(N). The space complexity depends on the implementation of the sorting algorithm used. `Arrays.sort()` in Java for primitive types uses a dual-pivot quicksort, which has an average space complexity of O(log N) for the recursion stack. The worst-case space complexity can be O(N).
Trade-offs
Pros
The greedy logic is simple and intuitive.
The implementation is straightforward.
It is a general approach that works for any range of numbers, as its performance only depends on the number of elements
N.
Cons
The time complexity is dominated by the sorting step, which is O(N log N). This can be slower than linear-time approaches if the range of values in the array is constrained and allows for faster sorting methods like counting sort.
This approach improves upon the sorting-based method by leveraging the constraint on the range of values in nums (0 <= nums[i] <= 10^5). Instead of a general-purpose O(N log N) sort, we can use a technique similar to counting sort to achieve a linear time complexity. We use a boolean array to mark the presence of each number and then iterate through this array to greedily form partitions.
Algorithm
- If
numsis empty, return 0. - Given the constraint
0 <= nums[i] <= 10^5, create a boolean arraypresentof size100001and initialize all its entries tofalse. - Iterate through the input array
nums: for each elementnum, setpresent[num] = true. - Initialize
partitions = 0and a pointercurrent = 0. - Loop while
currentis within the bounds of thepresentarray:- Find the next available number by advancing
currentuntilpresent[current]istrue. - If an available number is found (i.e.,
currentis still within bounds):- This number must start a new partition, so increment
partitions. - This new partition can contain all numbers up to
current + k. Therefore, we can jump our pointer past this range by settingcurrent = current + k + 1to find the start of the next partition.
- This number must start a new partition, so increment
- Find the next available number by advancing
- Return the final
partitionscount.
Walkthrough
This method avoids the O(N log N) sorting cost by using an auxiliary data structure to record which numbers are present in the input array. Given the constraint 0 <= nums[i] <= 10^5, a boolean array is a very efficient choice for this purpose.
First, we create a boolean array, present, of size 100001. We then make a single pass through the input nums array, and for each number x, we mark its corresponding index in the present array as true (i.e., present[x] = true). This gives us a sorted, duplicate-free representation of the numbers in nums.
Next, we apply the same greedy strategy as in the sorting approach, but on the present array. We iterate through the present array with a pointer, current. When we find the first current for which present[current] is true, we know this is the smallest number that needs to be partitioned. We start a new partition (incrementing our count) with current as its minimum element. This partition can accommodate any present number up to current + k. Therefore, we can skip checking all indices in this range and continue our search for the next present number by jumping our pointer to current + k + 1.
This process is repeated until we have scanned the entire present array. The final count will be the minimum number of partitions.
class Solution { public int partitionArray(int[] nums, int k) { if (nums == null || nums.length == 0) { return 0; } // As per constraints 0 <= nums[i] <= 10^5, we can use a boolean array. boolean[] present = new boolean[100001]; for (int num : nums) { present[num] = true; } int partitions = 0; int current = 0; while (current < present.length) { // Find the first number for a new partition while (current < present.length && !present[current]) { current++; } // If we found a number that exists in the input if (current < present.length) { partitions++; // This partition covers numbers up to current + k. // The next search for a new partition should start from current + k + 1. current += k + 1; } } return partitions; }}Complexity
Time
O(N + M), where N is the number of elements in `nums` and M is the maximum possible value of an element. We iterate through `nums` once to populate the `present` array (O(N)), and then we iterate through the `present` array once (O(M)).
Space
O(M), where M is the maximum possible value of an element. We need to store the `present` array of size M+1. Given the constraint `0 <= nums[i] <= 10^5`, M is 100000, so the space is O(10^5), which is constant and acceptable.
Trade-offs
Pros
Achieves linear time complexity, which is asymptotically optimal.
Faster than the sorting-based approach for the given problem constraints.
Cons
This approach requires extra space proportional to the maximum possible value of an element (
M). IfMwere very large, this approach could be memory-intensive or infeasible.The approach is less general than sorting; its efficiency is contingent on the values of
numsbeing within a manageable range.
Solutions
Solution
public class Solution { public int PartitionArray(int[] nums, int k) { Array.Sort(nums); int ans = 1; int a = nums[0]; foreach(int b in nums) { if (b - a > k) { a = b; ans++; } } return ans; }}class Solution {public int partitionArray(int[] nums, int k) { Arrays.sort(nums); int ans = 1, a = nums[0]; for (int b : nums) { if (b - a > k) { a = b; ++ans; } } return ans; }}class Solution {public: int partitionArray(vector<int> &nums, int k) { sort(nums.begin(), nums.end()); int ans = 1, a = nums[0]; for (int &b : nums) { if (b - a > k) { a = b; ++ans; } } return ans; }};class Solution: def partitionArray(self, nums: List[int], k: int) -> int: nums . sort() ans, a = 1, nums[0] for b in nums: if b - a > k: a = b ans += 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.