LeetCode 3920 - Maximize Fixed Points After Deletions
The problem asks us to maximize the number of fixed points in an array after performing deletions. A fixed point is an index i such that nums[i] == i.
Difficulty: 🔴 Hard
Topics: Array, Binary Search, Sorting
Solution
Problem Understanding
The problem asks us to maximize the number of fixed points in an array after performing deletions. A fixed point is an index i such that nums[i] == i. We are allowed to delete any number of elements from the array, and after each deletion, all remaining elements shift left, which reassigns the indices starting from 0.
In simpler terms, we want to transform the original array into a subsequence that has the maximum number of elements nums[i] equal to their index i in the new, shifted array. We are not limited in which elements we can remove, but removing elements may help align other elements to their "correct" index.
The input is an array of integers where each element is non-negative and can be as large as 100,000. The length of the array can also be up to 100,000, which rules out solutions with quadratic time complexity. The output is a single integer representing the maximum number of fixed points achievable.
Important edge cases include arrays where no element is initially a fixed point, arrays already fully aligned, arrays with repeated values, and arrays of length 1.
Approaches
Brute Force Approach: The naive way is to try all possible deletions of elements, checking for each subsequence how many fixed points are created. This approach guarantees correctness because it examines every configuration, but it is infeasible because the number of subsequences is exponential, $O(2^n)$, which is too slow for n up to $10^5$.
Optimal Approach: The key observation is that each element nums[i] can become a fixed point in the final array if we delete exactly nums[i] - i elements before it. This is because after deleting k elements before index i, nums[i] will shift left by k positions. Therefore, the problem reduces to finding the largest set of elements where the number of deletions required to make each a fixed point is consistent with the relative order.
Concretely, we can compute shift_needed = nums[i] - i for each element. If shift_needed is negative, it is impossible for that element to become a fixed point, because we cannot insert elements to shift it right. The maximum number of fixed points is the largest number of elements sharing the same non-negative shift_needed value.
This transforms the problem from exponential complexity to linear time with a hash map counting occurrences.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(2^n) | O(n) | Check all subsequences and count fixed points |
| Optimal | O(n) | O(n) | Count shift_needed = nums[i] - i and find the maximum frequency |
Algorithm Walkthrough
- Initialize an empty dictionary
shift_countto count how many elements require the same shift to become fixed points. - Iterate over the array with index
iand valuenum = nums[i]. - Compute
shift_needed = num - i. This represents the number of deletions before indexito makenumalign as a fixed point. - If
shift_neededis negative, skip this element, as it can never become a fixed point. - Otherwise, increment
shift_count[shift_needed]by 1. - After processing all elements, the maximum value in
shift_countgives the largest number of fixed points we can achieve. - Return this maximum value, or 0 if no element can be aligned.
Why it works: Each shift_needed value corresponds to a feasible number of deletions before an element. By counting the most frequent shift_needed, we are effectively aligning the largest possible subsequence of elements that can all become fixed points after the necessary deletions.
Python Solution
class Solution:
def maxFixedPoints(self, nums: list[int]) -> int:
from collections import defaultdict
shift_count = defaultdict(int)
for i, num in enumerate(nums):
shift_needed = num - i
if shift_needed >= 0:
shift_count[shift_needed] += 1
return max(shift_count.values(), default=0)
Explanation: We use a defaultdict to count each possible shift_needed. Negative shifts are ignored because they cannot be achieved with deletions. Finally, the largest count in the dictionary represents the maximum fixed points achievable.
Go Solution
func maxFixedPoints(nums []int) int {
shiftCount := make(map[int]int)
maxCount := 0
for i, num := range nums {
shiftNeeded := num - i
if shiftNeeded >= 0 {
shiftCount[shiftNeeded]++
if shiftCount[shiftNeeded] > maxCount {
maxCount = shiftCount[shiftNeeded]
}
}
}
return maxCount
}
Explanation: In Go, we use a map to store the frequency of each shiftNeeded. Negative shifts are ignored. We maintain maxCount during iteration to avoid a separate loop for the maximum.
Worked Examples
Example 1: nums = [0, 2, 1]
| i | nums[i] | shift_needed | count |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 1 | 2 | 1 | 1 |
| 2 | 1 | -1 | - |
Maximum count is 1 for shift 0 and 1 for shift 1. Return 2 because we can align elements with shifts 0 and 1 separately by deleting the appropriate elements.
Example 2: nums = [3, 1, 2]
| i | nums[i] | shift_needed | count |
|---|---|---|---|
| 0 | 3 | 3 | 1 |
| 1 | 1 | 0 | 1 |
| 2 | 2 | 0 | 2 |
Maximum count is 2 (shift 0). Return 2.
Example 3: nums = [1, 0, 1, 2]
| i | nums[i] | shift_needed | count |
|---|---|---|---|
| 0 | 1 | 1 | 1 |
| 1 | 0 | -1 | - |
| 2 | 1 | -1 | - |
| 3 | 2 | -1 | - |
Maximum count is 1 (shift 1). By deleting the first element, we align the remaining [0, 1, 2], producing 3 fixed points. Return 3.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass over the array, computing shift for each element and updating dictionary/map |
| Space | O(n) | Dictionary/map can store up to n distinct shift values in worst case |
The linear time complexity ensures the solution is feasible for arrays up to length 100,000.
Test Cases
# Provided examples
assert Solution().maxFixedPoints([0,2,1]) == 2 # delete nums[1]
assert Solution().maxFixedPoints([3,1,2]) == 2 # no deletion
assert Solution().maxFixedPoints([1,0,1,2]) == 3 # delete nums[0]
# Edge and boundary cases
assert Solution().maxFixedPoints([0]) == 1 # single element, already fixed
assert Solution().maxFixedPoints([1]) == 0 # single element, cannot align
assert Solution().maxFixedPoints([0,0,0,0]) == 1 # repeated zeros
assert Solution().maxFixedPoints([5,4,3,2,1,0]) == 1 # reverse order, delete elements to align 0
assert Solution().maxFixedPoints(list(range(100000))) == 100000 # already all fixed
| Test | Why |
|---|---|
| [0,2,1] | Basic deletion example |
| [3,1,2] | No deletion needed |
| [1,0,1,2] | Deletion aligns most elements |
| [0] | Single element, already fixed |
| [1] | Single element, cannot be fixed |
| [0,0,0,0] | Repeated elements |
| [5,4,3,2,1,0] | Reverse order |
| list(range(100000)) | Stress test for maximum size |
Edge Cases
1. Negative shift_needed: Elements where nums[i] - i < 0 can never be fixed points because we cannot insert elements to shift them right. The algorithm ignores these, preventing overcounting.
2. Repeated values: When multiple elements require the same shift, they may all contribute to the count. The algorithm correctly counts all occurrences, ensuring the maximum subsequence is chosen.
3. Single element arrays: Arrays of length 1 are handled naturally. If the single element is 0, it is a fixed point; otherwise, it is not. This ensures boundary cases do not cause index errors.