Create n smaller arrays and an array of n indices. There is one index for each of the small arrays; they are all initialized to zero. Iterate over the large sorted array, at each step choosing one of the small arrays to put the value into. If the small array is already 'full' (its index is already equal to the length of the array), pick the next small array that is not yet full. Insert the value from the large array into the small array at the current value of its index, and then increment the index.
This will require a fast random number generator and perhaps a clever way of taking the completed subarrays out of the rotation, but it is linear in the large sorted array and requires no sorting.
In Java a two-dimensional array is an array of arrays, and we can leverage that to alter which subarrays each row is pointing to on the fly. The idea here is that subarrays is an array containing all the subarrays, and the references to them never change, but candidate starts out with references to all of them and gradually adjusts itself so that only the arrays that are still not full are referenced.
int[][] splitSorted(int[] array, int n) {
if (array.length % n != 0) {
throw new IllegalArgumentException(
n + " does not divide " + array.length + " evenly");
}
int size = array.length / n; // size of each subarray
int[][] subarrays = new int[n][size];
int[][] candidate = Arrays.copyOf(subarrays, subarrays.length);
int[] index = new int[n];
Random rand = new Random();
for (int i : array) {
int which = rand.nextInt(n);
candidate[which][index[which]] = i; // insert into a subarray
++index[which];
if (index[which] == size) { // one has maxed out
--n; // remove it from consideration
candidate[which] = candidate[n];
index[which] = index[n];
}
}
return subarrays;
}
When an array is full, we swap the one at the end of candidate into its place, so that the first rows contain the ones we're still trying to fill. Note that candidate completely loses track of the filled arrays. That's fine because they're still referenced by subarrays, which never changes. (We avoid having it alias candidate by taking a shallow copy with Arrays::copyOf when we create candidate.) We also have to swap the index for the array we're swapping.
Note also that the on the two lines that do the swap, which and n could be identical. An if could avoid setting them in this case, but setting them is redundant, so I prefer to keep the code simpler. n is decremented so the next time around the loop, we will pick a random number in a smaller range; only arrays that still need to be filled are considered. Finally, we return an array of all the filled subarrays.
It should be clear, but I will just underscore that because we put each value from the original large sorted array into one and only one subarray, and because we put them there in order, that the uniqueness and sorted constraints of the original array will be maintained on the subarrays.