LeetCode 798 - Smallest Rotation with Highest Score
The problem asks us to determine the optimal rotation of an array nums so that the resulting array achieves the highest possible score, where a score is defined as the number of elements that are less than or equal to their index after rotation.
Difficulty: 🔴 Hard
Topics: Array, Prefix Sum
Solution
Problem Understanding
The problem asks us to determine the optimal rotation of an array nums so that the resulting array achieves the highest possible score, where a score is defined as the number of elements that are less than or equal to their index after rotation. In other words, after rotating the array by some k, an element at position i contributes 1 point if nums[i] <= i.
The input is a zero-indexed array of integers where each element is between 0 and nums.length - 1. The output is a single integer k representing the smallest rotation index that yields the maximum score. The constraints indicate the array can be large, up to 10^5 elements, which rules out brute-force solutions that compute scores for all possible rotations individually, since that would require O(n^2) operations.
Important edge cases include arrays that are already in ascending order, arrays where all elements are the same, arrays where multiple rotations yield the same maximal score, and arrays of length 1. The problem guarantees valid input within the bounds, so we do not need to handle negative numbers or out-of-range values.
Approaches
The brute-force approach involves iterating over all possible rotations from 0 to n-1, applying the rotation, and then computing the score by checking each element against its index. This is simple to implement and guaranteed to be correct but has time complexity O(n^2) because each of the n rotations requires inspecting all n elements, which is too slow for n = 10^5.
The optimal approach uses a key observation: instead of calculating the score for each rotation individually, we can track how the score changes as we rotate. Each element nums[i] contributes a point for rotations k where (i - k + n) % n >= nums[i], which can be expressed as a range in modular arithmetic. By recording the start and end of these ranges as "score changes" in an auxiliary array and computing a prefix sum, we can determine the rotation that gives the maximal score in O(n) time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(n) | Rotate the array for all k and compute score each time |
| Optimal | O(n) | O(n) | Use difference array to track score changes efficiently |
Algorithm Walkthrough
- Initialize an array
changeof lengthnwith all zeros. This array will track how the score changes as we rotate. - For each index
iinnums, compute the rotation index range[start, end)where the score contribution ofnums[i]decreases. Specifically, ifnums[i] > 0, the score decreases when rotated beyond(i - nums[i] + 1 + n) % n. - Update the
changearray: incrementchange[start]and decrementchange[end]to reflect the range where the element stops contributing to the score. - Iterate through the
changearray, computing a running prefix sum to track the total score for each rotationk. - Keep track of the maximum score encountered and the smallest index
kthat achieves it. - Return the smallest
kwith the maximum score.
Why it works: Each element contributes to the score over a specific range of rotations. By using a difference array and prefix sums, we aggregate all individual contributions efficiently. The prefix sum guarantees we correctly compute the score for each rotation in O(n) time.
Python Solution
from typing import List
class Solution:
def bestRotation(self, nums: List[int]) -> int:
n = len(nums)
change = [0] * n
for i, num in enumerate(nums):
start = (i - num + 1 + n) % n
end = (i + 1) % n
change[start] += 1
change[end] -= 1
if start >= end:
change[0] += 1
max_score = score = 0
best_k = 0
for k in range(n):
score += change[k]
if score > max_score:
max_score = score
best_k = k
return best_k
Explanation: We first create a change array that tracks the effect of each number on the score as we rotate. For each number, we calculate the rotation range that decreases its score contribution. After adjusting for modular wrapping, we accumulate the total score for each rotation using a prefix sum and return the smallest rotation that gives the maximum score.
Go Solution
func bestRotation(nums []int) int {
n := len(nums)
change := make([]int, n)
for i, num := range nums {
start := (i - num + 1 + n) % n
end := (i + 1) % n
change[start]++
change[end]--
if start >= end {
change[0]++
}
}
maxScore, score, bestK := 0, 0, 0
for k := 0; k < n; k++ {
score += change[k]
if score > maxScore {
maxScore = score
bestK = k
}
}
return bestK
}
Go-specific notes: We use slices instead of arrays for dynamic allocation. Modulo operations handle wrapping correctly. No additional data structures are needed beyond the change slice.
Worked Examples
Example 1: nums = [2,3,1,4,0]
| i | nums[i] | start | end | change update |
|---|---|---|---|---|
| 0 | 2 | 4 | 1 | change[4]+=1, change[1]-=1, change[0]+=1 |
| 1 | 3 | 0 | 2 | change[0]+=1, change[2]-=1 |
| 2 | 1 | 2 | 3 | change[2]+=1, change[3]-=1 |
| 3 | 4 | 0 | 4 | change[0]+=1, change[4]-=1 |
| 4 | 0 | 0 | 0 | change[0]+=1, change[0]-=1 |
Prefix sum produces scores for k=0..4: [2,3,3,4,3], so best_k = 3.
Example 2: nums = [1,3,0,2,4]
Prefix sum produces scores for k=0..4: [3,3,3,3,3], so best_k = 0.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We iterate through the array once to compute changes, then once to compute prefix sums. |
| Space | O(n) | The change array requires O(n) space. |
The optimal approach avoids computing the score for each rotation explicitly, resulting in a linear time algorithm suitable for large arrays.
Test Cases
# Provided examples
assert Solution().bestRotation([2,3,1,4,0]) == 3 # max score 4 at k=3
assert Solution().bestRotation([1,3,0,2,4]) == 0 # all rotations score 3, smallest k=0
# Edge cases
assert Solution().bestRotation([0]) == 0 # single element
assert Solution().bestRotation([0,0,0,0]) == 0 # all zeros, any k works
assert Solution().bestRotation([4,3,2,1,0]) == 1 # descending order
assert Solution().bestRotation([1,2,3,4,0]) == 4 # single max at end
| Test | Why |
|---|---|
| [2,3,1,4,0] | Checks standard case with varying scores |
| [1,3,0,2,4] | Checks multiple rotations yielding same score |
| [0] | Single element edge case |
| [0,0,0,0] | All elements identical edge case |
| [4,3,2,1,0] | Descending order, rotation affects score |
| [1,2,3,4,0] | Max at the end, affects optimal rotation |
Edge Cases
- Single element array: An array with one element, e.g.,
[0]. This could trip up naive modulo calculations. Our implementation handles it becausen = 1ensures modulo arithmetic and loops work correctly. - All elements the same: An array like
[0,0,0,0]may have multiple rotations yielding the same maximal score. The algorithm correctly returns the smallest index because we initializebest_kto 0 and update only when a strictly higher score is found. - Descending order array: An array like
[4,3,2,1,0]tests whether the rotation logic properly handles negative ranges and modulo wrapping. The modular arithmetic in computingstartensures ranges wrap correctly, and prefix sum aggregation correctly identifies the optimal rotation.