Title: Rearrange Array so that arr[i] Becomes arr[arr[i]]
If you are given an array where every value is itself a valid index into that same array, you can remap every slot to point at the value its index refers to, without allocating a second array. In this guide, we will build a clean, compilable Java program that rearranges an array so arr[i] becomes arr[arr[i]] for every index, using an encoding trick that keeps the whole operation in place.
You will learn why a naive rearrangement needs a second array, how a single multiply-and-mod trick lets you encode two values in one int slot, and how to walk through the algorithm one index at a time so the logic sticks. We will also cover the mistakes candidates make under interview pressure and the follow-up questions you should expect once the base solution is working.
Understanding the Problem
You are given an array arr of size n containing a permutation of the integers 0 to n - 1. You must transform the array so that, for every index i, the new value at arr[i] equals the old value that used to live at arr[arr[i]].
The obvious approach copies the original array, then fills a new array with copy[copy[i]] for each i. That works, but it costs O(n) extra space. The interesting version of this problem asks you to do the remapping in place, using only the original array and a constant number of extra variables.
The in-place trick relies on one property of the input: every value is already a valid index. That lets us temporarily store two numbers inside one int by encoding the old value and the new value together, then separating them again in a second pass.
Approach / Algorithm Steps
The core idea is to encode each original value and its target value into a single slot using n as a base, then decode every slot in a second pass. Follow these steps:
- Capture
n = arr.lengthonce, before any mutation, since every encode and decode step depends on this exact value. - Loop over every index
ifrom0ton - 1. - For each
i, readarr[arr[i]] % n. The modulo recovers the original value even if that slot was already encoded earlier in this same pass. - Add
(arr[arr[i]] % n) * ntoarr[i], so the slot now holdsoriginal value + n * target valuein a singleint. - After the encoding loop finishes, every slot holds both its old and new value packed together.
- Loop over every index
ia second time and dividearr[i]byn, discarding the remainder that held the old value. - Each slot now holds exactly
arr[arr[i]]from before the transformation, and the array has been rearranged with no extra array allocated.
Java Program
import java.util.Arrays;
public class RearrangeArray {
public static void rearrange(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
arr[i] += (arr[arr[i]] % n) * n;
}
for (int i = 0; i < n; i++) {
arr[i] /= n;
}
}
public static void main(String[] args) {
int[] arr = {3, 2, 0, 1};
System.out.println("Original array: " + Arrays.toString(arr));
rearrange(arr);
System.out.println("Rearranged array: " + Arrays.toString(arr));
}
}Explanation of the Program
rearrangefirst storesnonce, since both the encode and decode loops need the exact same value to stay consistent.- The first
forloop never writes a plain new value. It always adds(arr[arr[i]] % n) * nto the existingarr[i], which packs the future value into the high part of the number while the low part (anything less thann) still reflects the original value. - The
% nonarr[arr[i]]is doing the real work: if that slot has already been encoded earlier in the same loop, it now holdsoriginalValue + n * something, and taking it modulonstrips thatn * somethingback off, leaving the original value exactly as it was before any encoding touched it. - The second
forloop is purely a cleanup pass. Dividing everyarr[i]bynthrows away the remainder, which is exactly the part that held the old value, and keeps the quotient, which is exactly the new value that was packed in during the first loop. mainjust prints the array before and after callingrearrange, so you can see the transformation happen on a 4-element permutation.
Step-by-Step Trace
Trace rearrange on arr = [4, 0, 2, 1, 3]. Index 2 maps to itself, which is a useful case to watch, and the expected final array is [3, 4, 2, 0, 1].
| i | arr[i] before | arr[arr[i]] read | decoded (% n) | arr[i] after encoding |
|---|---|---|---|---|
| 0 | 4 | arr[4] = 3 | 3 | 19 |
| 1 | 0 | arr[0] = 19 | 4 | 20 |
| 2 | 2 | arr[2] = 2 | 2 | 12 |
| 3 | 1 | arr[1] = 20 | 0 | 1 |
| 4 | 3 | arr[3] = 1 | 1 | 8 |
Notice row i = 3 reads arr[1], which was already encoded back in row i = 1; the % n recovers the original 0 from the encoded 20 instead of treating 20 as a literal value. After the encoding loop, arr holds [19, 20, 12, 1, 8]. Dividing every slot by 5 in the second loop gives [3, 4, 2, 0, 1], the expected result. At the end of the encoding loop, every arr[i] holds originalValue + n * targetValue, so a single division pass is enough to reveal targetValue everywhere at once.
Examples
Example 1: Identity Permutation
When arr[i] == i for every index, the rearrangement is a no-op, because arr[arr[i]] is just arr[i] again.
int[] arr = {0, 1, 2, 3};
rearrange(arr);
System.out.println(Arrays.toString(arr));
// Output: [0, 1, 2, 3]Example 2: A Single 3-Cycle
arr = [2, 0, 1] is one cycle of length 3. Every index chases the next one around the cycle before landing back on itself.
int[] arr = {2, 0, 1};
rearrange(arr);
System.out.println(Arrays.toString(arr));
// Output: [1, 2, 0]Example 3: Two Overlapping 2-Cycles
arr = [1, 3, 0, 2] contains two independent swaps happening at the same time: index 0 and index 2 trade values, and index 1 and index 3 trade values.
int[] arr = {1, 3, 0, 2};
rearrange(arr);
System.out.println(Arrays.toString(arr));
// Output: [3, 2, 1, 0]Common Mistakes to Avoid
This encoding trick is compact, which also makes it easy to get subtly wrong. Watch for these mistakes:
- Skipping the modulo when reading – reading
arr[arr[i]]without% nonce that slot has already been encoded in the same pass returns the inflated value instead of the original one, corrupting every index that chains through it. Always readarr[arr[i]] % n, never the raw value. - Dividing inside the encoding loop – dividing
arr[i]bynright after encoding it destroys the original value before a later index can read it througharr[arr[i]], so any index that points at an earlier slot gets a wrong result. Keep the encode and decode steps in two completely separate loops. - Applying this to non-permutation input – if
arrcontains duplicates or values outside0ton - 1,arr[arr[i]]either collides with another index or throwsArrayIndexOutOfBoundsException. Confirm the input is a true permutation of0ton - 1before relying on this O(1)-space trick. - Ignoring integer overflow for large n – the encoding step multiplies a value by
n, so for a large array this can exceedInteger.MAX_VALUEand silently wrap around, producing a wrong decode. Switch to along[]for largen, or fall back to theO(n)extra-array approach.
Edge Cases to Test
Walk through these input shapes before calling the implementation done:
- Empty array
- Single-element array (
arr = [0]) - Identity permutation (
arr[i] == ifor every index) - Array containing a self-mapped index among other cycles
- Values outside the
0ton - 1range - Duplicate values (not a valid permutation)
- Large
nwhere encoding risksintoverflow - Null array reference
Additional Considerations
The algorithm runs in O(n) time, two linear passes over the array, and uses O(1) extra space beyond the input array itself, since the encoding happens in place. This is the main advantage over the naive copy-based approach, which is also O(n) time but needs an O(n) auxiliary array.
The trade-off is fragility. The technique only works because every value doubles as a valid index, which lets you pack two numbers into one int without losing information. It does not generalize to arbitrary integer arrays, and it temporarily produces values up to roughly n * n, so it is only safe when n * n fits comfortably inside the chosen numeric type.
Interview Follow-up Questions
Once the base solution is working, expect the interviewer to push further:
- What if the array is not guaranteed to be a permutation of 0 to n – 1? – fall back to an auxiliary array of size
n, fill it witharr[arr[i]]for everyiusing the original values, then copy it back; this costsO(n)extra space but handles arbitrary integers safely. - What if n is large enough that the encoding multiplication overflows int? – switch the array to
long[], or process the array in blocks small enough that each block’sn * nstays withinintrange. - How would you verify, without extra space, that the input really is a permutation of 0 to n – 1? – use the sign bit of each slot as a visited marker: negate
arr[v]the first time valuevis seen, and if you ever encounter a value whose target slot is already negative, it is a duplicate. - What if you needed to apply this remapping to an array too large to fit in memory? – this calls for an external, pass-based approach: store
(index, value)pairs on disk sorted by index, then produce the remapped output with a single sequential merge pass instead of random access by index.
Conclusion
The arr[i] becomes arr[arr[i]] problem is a good test of whether you can see past the obvious O(n) space solution and find a way to pack two values into one slot safely. Once you understand why the % n read matters and why the encode and decode steps must stay in separate loops, the implementation itself is only a handful of lines.
Try adapting rearrange to validate its input first, or to detect and report cycle lengths as it walks the array, to build more confidence with this encoding style of in-place trick. You can learn more about array manipulation problems like this one in our Arrays category.



