LeetCode 3397 - Maximum Number of Distinct Elements After Operations
The problem asks us to maximize the number of distinct elements in an integer array nums by performing a limited set of operations. Each element can be modified at most once by adding an integer in the range [-k, k].
Difficulty: 🟡 Medium
Topics: Array, Greedy, Sorting
Solution
Problem Understanding
The problem asks us to maximize the number of distinct elements in an integer array nums by performing a limited set of operations. Each element can be modified at most once by adding an integer in the range [-k, k]. The goal is to determine the largest possible count of unique numbers after applying these operations optimally.
In other words, we are allowed to "shift" each number slightly (up to k in either direction) to try to eliminate duplicates or create gaps between repeated numbers. The input nums can have up to 10^5 elements and very large numbers (1 <= nums[i] <= 10^9). The parameter k can also be very large (0 <= k <= 10^9), meaning some elements could potentially move far enough to create distinct values.
Key edge cases include arrays where all elements are identical, arrays already distinct, k = 0 where no modification is allowed, and very large k values which could allow a large "spread" of numbers. Any naive approach attempting to enumerate all possibilities would fail due to the high range and input size.
Approaches
Brute Force
The brute-force approach would consider all possible numbers that each element could be transformed into, which would be 2k + 1 options per element. This quickly becomes infeasible because for n elements, there are (2k+1)^n possibilities. This approach is correct in principle but impractical due to combinatorial explosion.
Optimal Approach
The key insight is that to maximize distinct numbers, we need to spread duplicates apart. The algorithm relies on sorting the array and then greedily adjusting duplicates minimally, either increasing or decreasing them, to avoid collisions while respecting the [-k, k] limit. Using a set to track existing numbers and a count of "used operations" allows us to compute the maximum distinct elements efficiently.
The optimal approach is to:
- Sort the array.
- Track the current set of distinct numbers.
- For each element, adjust it within
[-k, k]to avoid duplicates already in the set. - Count how many unique numbers can be achieved.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((2k+1)^n) | O(n) | Tries all possible adjustments for each element |
| Optimal | O(n log n) | O(n) | Sort array and greedily assign numbers to avoid duplicates |
Algorithm Walkthrough
- Sort
nums. Sorting allows us to handle duplicates sequentially and makes it easier to determine if a number can be adjusted to avoid collisions. - Initialize a
setof used numbers. This tracks all the numbers that have already been assigned after possible adjustment. - Iterate through each element
numinnums:
-
If
numis not in theusedset, add it directly. -
If
numis inused, try to adjust it: -
Check if
num - kornum + kcan be used without colliding with existing numbers. -
Pick the adjustment that is feasible within the operation limits.
-
Skip numbers that cannot be adjusted to a unique value.
- Return the size of the
usedset. This represents the maximum number of distinct numbers achievable.
Why it works: Sorting ensures we handle duplicates in sequence. The greedy adjustment ensures each element is moved only as much as necessary to avoid collisions. By always checking for feasible positions in [-k, k] relative to the current number and existing numbers, we guarantee maximal distinct numbers without violating the operation limit.
Python Solution
from typing import List
class Solution:
def maxDistinctElements(self, nums: List[int], k: int) -> int:
nums.sort()
used = set()
for num in nums:
if num not in used:
used.add(num)
else:
# Try to move down within [-k, k]
for delta in range(1, k + 1):
if num - delta not in used:
used.add(num - delta)
break
elif num + delta not in used:
used.add(num + delta)
break
return len(used)
This solution first sorts the array to handle duplicates systematically. For each number, it checks whether the number is already in the used set. If it is, it tries to move the number down or up within the allowed range until a free slot is found. The size of used gives the maximum distinct count.
Go Solution
import "sort"
func maxDistinctElements(nums []int, k int) int {
sort.Ints(nums)
used := make(map[int]bool)
for _, num := range nums {
if !used[num] {
used[num] = true
} else {
for delta := 1; delta <= k; delta++ {
if !used[num-delta] {
used[num-delta] = true
break
} else if !used[num+delta] {
used[num+delta] = true
break
}
}
}
}
return len(used)
}
In Go, we use a map[int]bool to track used numbers. Sorting and iteration logic is identical. Go requires explicit map initialization, and integer overflows are not a concern with the problem constraints.
Worked Examples
Example 1: nums = [1,2,2,3,3,4], k = 2
| Step | num | used set | Notes |
|---|---|---|---|
| 1 | 1 | {1} | First element added |
| 2 | 2 | {1,2} | Unique, added directly |
| 3 | 2 | {0,1,2} | Duplicate adjusted down by 2 |
| 4 | 3 | {0,1,2,3} | Unique, added |
| 5 | 3 | {0,1,2,3,4} | Duplicate adjusted up by 1 |
| 6 | 4 | {0,1,2,3,4,5} | Unique after adjustment |
Output: 6
Example 2: nums = [4,4,4,4], k = 1
| Step | num | used set | Notes |
|---|---|---|---|
| 1 | 4 | {4} | Added directly |
| 2 | 4 | {3,4} | Adjusted down by 1 |
| 3 | 4 | {3,4,5} | Adjusted up by 1 |
| 4 | 4 | {3,4,5} | Cannot adjust, skipped |
Output: 3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n + n*k) | Sorting takes O(n log n), iteration with k adjustments takes O(n*k) in worst case |
| Space | O(n) | Set/map stores all distinct numbers |
Sorting dominates for large n. The inner loop may run up to k times per element, but for small k relative to n, it is efficient.
Test Cases
# Provided examples
assert Solution().maxDistinctElements([1,2,2,3,3,4], 2) == 6
assert Solution().maxDistinctElements([4,4,4,4], 1) == 3
# Edge cases
assert Solution().maxDistinctElements([1,1,1,1,1], 0) == 1 # No adjustment possible
assert Solution().maxDistinctElements([1,2,3,4,5], 0) == 5 # Already distinct
assert Solution().maxDistinctElements([1], 100) == 1 # Single element
assert Solution().maxDistinctElements([1,1,2,2,3,3], 100) == 6 # Large k spreads duplicates
| Test | Why |
|---|---|
| [1,2,2,3,3,4], 2 | Example from problem, tests multiple duplicates |
| [4,4,4,4], 1 | Example from problem, small k |
| [1,1,1,1,1], 0 | Tests k=0, no movement allowed |
| [1,2,3,4,5], 0 | Already distinct, ensures no unnecessary adjustment |
| [1], 100 | Single element edge case |
| [1,1,2,2,3,3], 100 | Large k ensures maximum spreading |
Edge Cases
All elements identical, k = 0: No adjustments are allowed, the result is always 1 regardless of array length. The implementation correctly adds the first element and skips duplicates.
Large k values: If k is large, duplicates can be spread widely. Our algorithm iterates over potential shifts and guarantees uniqueness within the allowed range.
Already distinct array: If the input has no duplicates, the algorithm should not attempt any unnecessary adjustments. Sorting preserves the original order of checks, and the used set prevents accidental collisions.