3865. Reverse K Subarrays 🔒
Problem Description
You are given an integer array nums of length n and an integer k.
Your task is to partition the array into k contiguous subarrays, where each subarray has the same length, and then reverse each of these subarrays individually.
Since the array is split into k equal parts, each subarray will have a length of m = n / k. The problem guarantees that n is divisible by k, so the array can always be evenly divided.
After reversing every subarray in place, you need to return the resulting array.
For example, if nums = [1, 2, 3, 4, 5, 6] and k = 2, the array is split into two subarrays [1, 2, 3] and [4, 5, 6], each of length m = 6 / 2 = 3. After reversing each subarray, you get [3, 2, 1] and [6, 5, 4], so the final answer is [3, 2, 1, 6, 5, 4].
How We Pick the Algorithm
Why Simulation / Basic DSA?
This problem maps to Simulation / Basic DSA through a short path in the full flowchart.
Directly follow the steps: compute block size, iterate blocks, reverse each block in place with two pointers.
Open in FlowchartIntuition
The key observation is that the operation we need to perform is described directly by the problem statement, so we can simply follow the instructions step by step.
First, we figure out how long each subarray should be. Since the array of length n is divided into k equal parts, each part has length m = n / k. Because n is guaranteed to be divisible by k, this division is always clean and every subarray is the same size.
Next, we notice that the subarrays do not overlap and together they cover the entire array. The first subarray starts at index 0, the second at index m, the third at index 2m, and so on. In general, each subarray begins at an index that is a multiple of m. This means we can walk through the array using a step size of m, and at each position i, the current subarray spans the range [i, i + m).
For each of these ranges, the only thing left to do is reverse the elements within it. By reversing each block independently and leaving the rest of the array untouched, we naturally build up the final result. Since the blocks are processed one after another and never interfere with each other, performing the reversals in place directly gives us the answer.
Pattern Learn more about Two Pointers patterns.
Solution Approach
Solution 1: Simulation
Since we need to partition the array into k subarrays of equal length, the length of each subarray is m = n / k. We can use a loop to traverse the array with a step size of m, and in each iteration, reverse the current subarray.
Let's break down the implementation step by step:
-
Compute the dimensions. First, we get the total length of the array
n = len(nums), then calculate the length of each subarraym = n // k. This tells us how many elements belong to each block. -
Traverse the array in blocks. We use a loop
for i in range(0, n, m), which makesitake the values0, m, 2m, 3m, .... Each value ofimarks the starting index of one subarray. Because the step size is exactlym, every subarray is visited exactly once and they perfectly tile the entire array. -
Reverse each block in place. For each starting index
i, the current subarray occupies the slicenums[i : i + m]. We reverse it using slice notationnums[i : i + m][::-1]and assign it back to the same positions withnums[i : i + m] = nums[i : i + m][::-1]. This swaps the order of the elements within that block while leaving everything outside the block unchanged. -
Return the result. After all blocks have been reversed,
numsitself holds the final answer, so we simplyreturn nums.
class Solution:
def reverseSubarrays(self, nums: list[int], k: int) -> list[int]:
n = len(nums)
m = n // k
for i in range(0, n, m):
nums[i : i + m] = nums[i : i + m][::-1]
return nums
Complexity Analysis:
- Time complexity:
O(n), wherenis the length of the arraynums. Each element is touched exactly once during the reversal across all blocks. - Space complexity:
O(1)(ignoring the temporary space used by slicing), since the reversals are performed in place and we do not use any extra data structures that grow with the input size.
Example Walkthrough
Let's trace through a small example to see how the Simulation approach works.
Input: nums = [10, 20, 30, 40], k = 2
Step 1 — Compute the dimensions.
n = len(nums) = 4m = n // k = 4 // 2 = 2
So the array is split into 2 subarrays, each of length 2. The blocks are [10, 20] and [30, 40].
Step 2 — Traverse the array in blocks.
The loop for i in range(0, 4, 2) makes i take the values 0 and 2. Each value marks the starting index of one subarray.
Step 3 — Reverse each block in place.
Iteration 1 (i = 0):
- The current block is the slice
nums[0 : 2], which is[10, 20]. - Reversing it gives
nums[0 : 2][::-1] = [20, 10]. - Assign it back:
nums[0 : 2] = [20, 10]. - The array now looks like
[20, 10, 30, 40].
Iteration 2 (i = 2):
- The current block is the slice
nums[2 : 4], which is[30, 40]. - Reversing it gives
nums[2 : 4][::-1] = [40, 30]. - Assign it back:
nums[2 : 4] = [40, 30]. - The array now looks like
[20, 10, 40, 30].
The two blocks never overlap, so reversing one leaves the other untouched.
Step 4 — Return the result.
After both blocks have been reversed, nums = [20, 10, 40, 30] is the final answer.
i | Block (before) | Block (after reverse) | Array after step |
|---|---|---|---|
0 | [10, 20] | [20, 10] | [20, 10, 30, 40] |
2 | [30, 40] | [40, 30] | [20, 10, 40, 30] |
This confirms that walking through the array with step size m and reversing each block independently produces the correct partitioned-and-reversed result.
Solution Implementation
1class Solution:
2 def reverseSubarrays(self, nums: list[int], k: int) -> list[int]:
3 # Total number of elements in the array
4 n = len(nums)
5
6 # Size of each chunk to reverse:
7 # integer division of the length by k
8 chunk_size = n // k
9
10 # Iterate over the array in steps of chunk_size,
11 # so that each iteration handles one chunk
12 for start in range(0, n, chunk_size):
13 # Reverse the current slice in place using
14 # the reversed slice [::-1]
15 nums[start : start + chunk_size] = nums[start : start + chunk_size][::-1]
16
17 # Return the modified array
18 return nums
191class Solution {
2 /**
3 * Splits the array into blocks of size (length / k) and reverses
4 * each block in place using a two-pointer approach.
5 *
6 * @param nums the input array to be processed
7 * @param k divisor used to determine the block size
8 * @return the array after reversing each block in place
9 */
10 public int[] reverseSubarrays(int[] nums, int k) {
11 int length = nums.length;
12 // Size of each block to reverse
13 int blockSize = length / k;
14
15 // Iterate over the array one block at a time
16 for (int start = 0; start < length; start += blockSize) {
17 // Set the two pointers at the boundaries of the current block
18 int left = start;
19 int right = start + blockSize - 1;
20
21 // Reverse the current block by swapping elements
22 // from the outside toward the center
23 while (left < right) {
24 int temp = nums[left];
25 nums[left++] = nums[right];
26 nums[right--] = temp;
27 }
28 }
29
30 return nums;
31 }
32}
331class Solution {
2public:
3 vector<int> reverseSubarrays(vector<int>& nums, int k) {
4 // Total number of elements in the input array
5 int n = nums.size();
6
7 // Size of each block to be reversed (integer division)
8 int blockSize = n / k;
9
10 // Iterate over the array, advancing one block at a time
11 for (int start = 0; start < n; start += blockSize) {
12 // Set two pointers at the ends of the current block
13 int left = start;
14 int right = start + blockSize - 1;
15
16 // Reverse the elements within this block in place
17 while (left < right) {
18 swap(nums[left++], nums[right--]);
19 }
20 }
21
22 // Return the modified array
23 return nums;
24 }
25};
26```
27
28**A few notes on potential concerns:**
29
301. **Edge case (`k == 0` or `blockSize == 0`):** If `k > n`, then `blockSize` becomes `0`, and `start += blockSize` would create an infinite loop. The original code shares this risk—worth guarding if such input is possible.
31
322. **Remainder handling:** When `n` is not divisible by `m`, the last partial block (size `n % blockSize`) is still reversed. The reverse loop naturally handles a partial block since `right` is bounded by the iteration's `start`, but note it does **not** clamp `right` to `n - 1`. If `start + blockSize - 1` could exceed `n - 1` on the final block, you'd want `right = min(start + blockSize - 1, n - 1)` for safety. In the divisible case this never triggers.
33
34A defensive variant would add:
35```cpp
36if (blockSize == 0) return nums; // guard against infinite loop
37int right = min(start + blockSize - 1, n - 1); // clamp last block
381/**
2 * Divides the array into consecutive subarrays (each of size Math.floor(n / k))
3 * and reverses each subarray in place, then returns the modified array.
4 *
5 * @param nums - The input array of numbers to be processed.
6 * @param k - Used to determine the size of each subarray (size = floor(n / k)).
7 * @returns The same array reference after in-place reversal of each subarray.
8 */
9function reverseSubarrays(nums: number[], k: number): number[] {
10 // Total number of elements in the array.
11 const n: number = nums.length;
12
13 // Size of each subarray group to be reversed.
14 const groupSize: number = Math.floor(n / k);
15
16 // Iterate over the array one group at a time, stepping by groupSize.
17 for (let start = 0; start < n; start += groupSize) {
18 // Two pointers: left at the group's start, right at the group's end.
19 let left: number = start;
20 let right: number = start + groupSize - 1;
21
22 // Reverse the current group in place by swapping inward.
23 while (left < right) {
24 // Swap the elements at the left and right pointers.
25 const temp: number = nums[left];
26 nums[left++] = nums[right];
27 nums[right--] = temp;
28 }
29 }
30
31 // Return the modified array (same reference as the input).
32 return nums;
33}
34Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the arraynums. The loop iterates over the array in steps ofm, and for each step, the slice reversalnums[i : i + m][::-1]takesO(m)time. Since there are approximatelyn / miterations and each processesmelements, the total work isO(n / m × m) = O(n). Every element is touched a constant number of times. -
Space Complexity:
O(1). Although the slicenums[i : i + m][::-1]temporarily creates a copy of at mostmelements, in the context of the reference answer this is treated as constant extra space, and no additional space proportional to the full input size is allocated.
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Confusing k (number of subarrays) with m (length of each subarray)
The most frequent mistake is misinterpreting what k represents. In this problem, k is the number of subarrays, not the length of each one. This leads people to write the loop step incorrectly.
Incorrect code:
class Solution:
def reverseSubarrays(self, nums: list[int], k: int) -> list[int]:
n = len(nums)
# WRONG: treating k as the chunk size and stepping by k
for start in range(0, n, k):
nums[start : start + k] = nums[start : start + k][::-1]
return nums
For nums = [1, 2, 3, 4, 5, 6] and k = 2, this would reverse chunks of size 2 ([2,1,4,3,6,5]) instead of splitting into 2 chunks of size 3 ([3,2,1,6,5,4]).
Solution: Always compute the chunk size first with m = n // k, and use m (not k) as both the loop step and the slice width.
n = len(nums)
m = n // k # chunk size derived from k
for start in range(0, n, m): # step by m, the chunk size
nums[start : start + m] = nums[start : start + m][::-1]
Pitfall 2: Off-by-one errors when reversing manually
If you choose to reverse with explicit index pointers instead of slicing, it's easy to mismanage the boundaries and either skip the last element or run past the chunk's end.
Incorrect code:
for start in range(0, n, m):
left, right = start, start + m # WRONG: right should be start + m - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
Here right = start + m points one past the last valid index of the chunk, corrupting elements from the next block.
Solution: Set the right pointer to start + m - 1, the actual last index of the chunk.
for start in range(0, n, m):
left, right = start, start + m - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
Pitfall 3: Assuming the input is non-empty or that k is valid
When k equals 0, the line m = n // k raises a ZeroDivisionError. While the problem guarantees n is divisible by k, defensive code should not silently break on edge inputs.
Solution: Validate or rely on the stated constraints. If you want robustness, guard against k == 0:
if k == 0 or n == 0: return nums m = n // k
Pitfall 4: Mutating the input when an unmodified copy is needed
This solution reverses nums in place, so the original array passed by the caller is altered. If the caller still needs the original ordering afterward, this side effect can cause subtle bugs.
Solution: Work on a copy when the original must be preserved.
result = nums[:] # shallow copy
n, m = len(result), len(result) // k
for start in range(0, n, m):
result[start : start + m] = result[start : start + m][::-1]
return result
Choose in-place modification only when you're certain the caller doesn't depend on the original array.
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapA person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Two Pointers Technique Explained If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe width 560 height 315 src https www youtube nocookie com embed rQhNcycbf8w si lE7qtd1h_JSQwGpW title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!