LeetCode 3886 - Sum of Sortable Integers
The problem asks us to find all integers k that divide the length of the input array nums and for which the array can be sorted into non-decreasing order using a very specific operation.
Difficulty: 🔴 Hard
Topics: Array, Math, Sorting, Enumeration
Solution
Problem Understanding
The problem asks us to find all integers k that divide the length of the input array nums and for which the array can be sorted into non-decreasing order using a very specific operation. The operation is to partition nums into consecutive subarrays of length k and then cyclically rotate each subarray independently any number of times. A cyclic rotation allows elements to be shifted left or right with wrap-around, meaning the relative order of elements within each subarray can be changed but only within the subarray itself. The task is to sum all such k that allow the array to be sorted after these operations.
The input is an integer array nums of size n with elements between 1 and 10^5. The array length n can be as large as 10^5, which suggests that a naive brute-force approach checking every permutation would be too slow. The output is a single integer representing the sum of all sortable divisors of n.
Key observations include the importance of array length divisors, subarray rotations, and the fact that k = 1 is trivial but usually unsortable unless the array is already sorted. Edge cases include arrays that are already sorted, arrays in strictly descending order, or arrays of length 1 or 2.
Approaches
The brute-force approach would try every divisor k of n and for each one, generate all possible rotations of every subarray and check if the final array is sorted. This approach is correct in principle but extremely inefficient because generating all rotations for each subarray is exponential in k and n/k. This would quickly exceed time limits for n = 10^5.
The optimal approach relies on a key observation: a subarray of length k can be rotated arbitrarily, so the position modulo k in the sorted array must contain exactly the same elements as the position modulo k in the original array. Concretely, if we sort nums to get sorted_nums, then for each index i, nums[i] must appear somewhere in the group i % k in the sorted array. If this holds for all indices in all groups, then k is sortable. This reduces the problem to counting occurrences in modulo classes, which is linear for each divisor k.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n! * n/k) | O(n) | Generates all rotations for all divisors, too slow for large n |
| Optimal | O(n * sqrt(n)) | O(n) | Uses counting by modulo positions and compares with sorted array |
Algorithm Walkthrough
- Compute the sorted version of
numscalledsorted_nums. - Find all divisors of
nand store them in a list. - Initialize a variable
totalto accumulate the sum of sortablek. - For each divisor
k, createkbuckets (lists) representing positions modulok. - Assign elements of
numsinto their corresponding bucket according to index modulok. - Assign elements of
sorted_numsinto another set of buckets in the same way. - For each bucket, compare the multiset of original elements with the multiset of sorted elements.
- If all buckets match for a divisor
k, addktototal. - Return
total.
Why it works: The modulo buckets guarantee that cyclic rotations of size k cannot move elements out of their modulo class. Therefore, if the elements in each modulo class match the sorted array's corresponding positions, there exists a series of rotations to achieve the sorted array.
Python Solution
class Solution:
def sortableIntegers(self, nums: list[int]) -> int:
from collections import Counter
n = len(nums)
sorted_nums = sorted(nums)
# Find all divisors of n
divisors = []
for i in range(1, int(n ** 0.5) + 1):
if n % i == 0:
divisors.append(i)
if i != n // i:
divisors.append(n // i)
total = 0
for k in divisors:
buckets_orig = [[] for _ in range(k)]
buckets_sorted = [[] for _ in range(k)]
for idx, val in enumerate(nums):
buckets_orig[idx % k].append(val)
for idx, val in enumerate(sorted_nums):
buckets_sorted[idx % k].append(val)
if all(Counter(buckets_orig[i]) == Counter(buckets_sorted[i]) for i in range(k)):
total += k
return total
Explanation: The code first sorts nums and finds all divisors of n. For each divisor k, it partitions both nums and sorted_nums into k modulo-based buckets. Using Counter, it checks if each bucket contains exactly the same elements as the corresponding sorted bucket. If all buckets match, k is added to the total sum. The use of Counter allows handling repeated numbers correctly.
Go Solution
package main
func sortableIntegers(nums []int) int {
n := len(nums)
sortedNums := make([]int, n)
copy(sortedNums, nums)
// Sort the copy
sort.Ints(sortedNums)
// Find divisors
divisors := []int{}
for i := 1; i*i <= n; i++ {
if n%i == 0 {
divisors = append(divisors, i)
if i != n/i {
divisors = append(divisors, n/i)
}
}
}
total := 0
for _, k := range divisors {
bucketsOrig := make([]map[int]int, k)
bucketsSorted := make([]map[int]int, k)
for i := 0; i < k; i++ {
bucketsOrig[i] = make(map[int]int)
bucketsSorted[i] = make(map[int]int)
}
for idx, val := range nums {
bucketsOrig[idx%k][val]++
}
for idx, val := range sortedNums {
bucketsSorted[idx%k][val]++
}
sortable := true
for i := 0; i < k; i++ {
if len(bucketsOrig[i]) != len(bucketsSorted[i]) {
sortable = false
break
}
for key, count := range bucketsOrig[i] {
if bucketsSorted[i][key] != count {
sortable = false
break
}
}
if !sortable {
break
}
}
if sortable {
total += k
}
}
return total
}
Go-specific notes: Since Go does not have Counter, we use maps to count occurrences. Slices of maps represent the modulo buckets. Sorting is done with sort.Ints. Overflow is not a concern due to constraints.
Worked Examples
Example 1: nums = [3, 1, 2]
n = 3, divisors[1, 3]- Sorted array:
[1, 2, 3] k = 1: buckets[[3], [1], [2]]vs[[1], [2], [3]]→ not equalk = 3: buckets[[3, 1, 2]]vs[[1, 2, 3]]→ equal → sortable- Sum = 3
Example 2: nums = [7, 6, 5]
n = 3, divisors[1, 3]- Sorted array:
[5, 6, 7] k = 1: not equal,k = 3: not equal → sum = 0
Example 3: nums = [5, 8]
n = 2, divisors[1, 2]- Sorted array:
[5, 8] k = 1: equal → 1,k = 2: equal → 2 → sum = 3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * sqrt(n)) | Divisors of n are ≤ 2*sqrt(n). For each divisor, we build modulo buckets and compare them in O(n). |
| Space | O(n) | Storing buckets for nums and sorted_nums uses linear space. |
The algorithm scales well up to n = 10^5 due to linear processing per divisor and a small number of divisors.
Test Cases
# Example test cases
assert Solution().sortableIntegers([3,1,2]) == 3 # single sortable k=3
assert Solution().sortableIntegers([7,6,5]) == 0 # no sortable k
assert Solution().sortableIntegers([5,8]) == 3 # both k=1 and k=2 sortable
# Edge and additional cases
assert Solution().sortableIntegers([1]) == 1 # single element array
assert Solution().sortableIntegers([2,1]) == 2 # k=1 not sortable, k=2 sortable
assert Solution().sortableIntegers([1,2,3,