Reverse and rotate a slice in place
Two pointers, the three-reversal trick, and the slice aliasing that makes "in place" mean what it says.
- Write in-place reverse and rotate with two pointers and three reversals.
- Predict how reversing a sub-slice changes the original slice.
- Fix the divide-by-zero panic on an empty slice.
The problem
Reverse a slice in place. Then rotate it right by k positions — [1 2 3 4 5 6 7] rotated by 3 is [5 6 7 1 2 3 4] — using O(1) extra space.
Brute force first
Rotate right by 1, k times: save the last element, shift everything one place right, put the saved one at the front. Or copy into a new slice at (i+k) % n.
Shifting is O(n·k) time, O(1) space. The copy is O(n) time but O(n) extra space, which breaks the "in place" rule.
The approach
Reverse: two indices, one from each end, swap and move inwards. Go’s tuple assignment s[i], s[j] = s[j], s[i] needs no temporary.
Rotate: reverse the whole slice, then reverse the first k, then the rest. Each element moves a constant number of times: O(n) time, O(1) space. Taking k %= len(s) first handles k bigger than the length.
package mainimport ( "fmt" "slices")// reverse swaps from both ends towards the middle. In place, O(1) extra space.func reverse(s []int) { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { s[i], s[j] = s[j], s[i] }}// rotateRight by k using three reversals: whole, first k, rest.func rotateRight(s []int, k int) { if len(s) == 0 { return } k %= len(s) // k may be larger than len reverse(s) reverse(s[:k]) reverse(s[k:])}func main() { a := []int{1, 2, 3, 4, 5} reverse(a) fmt.Println(a) // [5 4 3 2 1] b := []int{1, 2, 3, 4, 5, 6, 7} rotateRight(b, 3) fmt.Println(b) // [5 6 7 1 2 3 4] // The stdlib already has reverse. Use it unless they ask you to write it. c := []int{1, 2, 3} slices.Reverse(c) fmt.Println(c) // [3 2 1] // The aliasing trap: a sub-slice shares the array, so the caller sees this. d := []int{1, 2, 3, 4} reverse(d[:2]) fmt.Println(d) // [2 1 3 4] // Reversing a string: convert to runes, not bytes, or UTF-8 breaks. r := []rune("héllo") slices.Reverse(r) fmt.Println(string(r)) // olléh}Dry run
Rotate [1 2 3 4 5 6 7] right by k = 3 with three reversals:
| step | what you reverse | slice after |
|---|---|---|
| 0 | (start) | [1 2 3 4 5 6 7] |
| 1 | whole slice | [7 6 5 4 3 2 1] |
| 2 | first k = 3 | [5 6 7 4 3 2 1] |
| 3 | the rest (index 3 onwards) | [5 6 7 1 2 3 4] |
Go-specific gotchas
- A slice is a view, not a copy.
reverse(s[:k])changes the caller’s array, which is exactly why the three-reversal trick works without returning anything. It is also why passing a sub-slice to a function you did not write can surprise you. - Need a copy?
slices.Clone(s). Assigningt := scopies only the header. - Strings are immutable. To reverse one, convert to
[]rune, not[]byte. Reversing bytes splits multi-byte UTF-8 characters and prints garbage. - Guard the empty slice before
k %= len(s), or you divide by zero and panic.
- Two pointers from the ends for reverse; three reversals for rotate.
- Sub-slices share the backing array, so in-place really is in place.
slices.Reverseexists — use it unless they ask you to write it.
k with the same trick, and write a table test with k = 0, k = len, and k > len.rotateRight(s []int, k int) that rotates s right by k positions in place, with no extra slice (the hidden tests check that it allocates nothing). k may be 0 or bigger than len(s), and s may be empty. Example: [1 2 3 4 5 6 7], k = 3 → [5 6 7 1 2 3 4].Tab indents. Press Esc, then Tab, to leave the editor.
- 1The main task: Rotate left by
kwith the same trick, and write a table test with k = 0, k = len, and k > len. - 2Predict the output:
a := []int{1,2,3,4}; b := a[1:3]; slices.Reverse(b); fmt.Println(a). - 3Reverse the words in a sentence ("the sky is blue" → "blue is sky the") with
strings.Fields,slices.Reverseandstrings.Join. - 4Dry run the three-reversal rotate on
[1 2 3 4 5]withk = 7. What iskafterk %= len(s), and what is the result? - 5Check whether a string is a palindrome using two pointers, so that
"नमन"and"madam"both work.