Don't Let Zeroes Hold You Back: Solving LeetCode 283 In-Place!
Hey LeetCoders and aspiring developers! 👋 Vansh here, and today we're tackling a classic array manipulation problem that looks simple on the surface but teaches us a ton about efficient, in-place algorithms: LeetCode 283, "Move Zeroes."
This problem is a fantastic entry point into the world of two-pointer techniques, a common pattern in competitive programming and everyday software development. So, let's dive in and conquer those pesky zeroes!
🚀 The Problem: Moving Zeroes
You're given an array of integers, let's call it nums. Your mission, should you choose to accept it, is to rearrange this array so that all the 0s are at the very end.
But there are two crucial rules:
- Maintain Relative Order: The non-zero elements must keep their original order. For instance, if you have
[1, 5, 3], they should remain1, 5, 3and not become5, 1, 3. - In-Place: You cannot create a new array. You must modify the given
numsarray directly. This is where the challenge (and fun!) truly begins.
Let's look at an example to make it crystal clear:
Example 1:
Input: nums = [0, 1, 0, 3, 12]
Output: [1, 3, 12, 0, 0]
Notice how 1, 3, and 12 keep their order, and the 0s are moved to the end.
Example 2:
Input: nums = [0]
Output: [0]
Simple enough, right?
🤔 The Intuition: What's the "Aha!" Moment?
When you first see this, you might think, "Okay, I'll find a zero, remove it, and append it to the end." But removing an element from an array means shifting all subsequent elements, which is really inefficient! Doing this multiple times would make our solution very slow.
The "aha!" moment comes when we flip our perspective. Instead of focusing on moving zeroes out of the way, what if we focused on moving non-zeroes to the front?
Imagine dividing your array into two sections:
- The "Good" Section: Where all the non-zero numbers belong, in their correct relative order.
- The "Junk" Section: Everything else, which will eventually become all zeroes.
We want to build up the "Good" section from the beginning of the array. As we iterate, if we find a non-zero number, we simply place it into the next available spot in our "Good" section. Any positions we skip (because they contained a zero) will eventually be filled by zeroes.
🧑💻 The Approach: Two Pointers to the Rescue!
This problem is a perfect candidate for the two-pointer technique. We'll use two pointers to manage our array sections:
-
nonZeroPtr(orcount): This pointer will track the next available position in our "Good" section where a non-zero element should be placed. It starts at index0. -
i(orcurrentPtr): This pointer will iterate through the entire array from left to right, checking each element.
Here's the step-by-step breakdown:
Initialize
nonZeroPtr = 0. This means our "Good" section starts empty, and the first non-zero element we find will go tonums[0].-
Iterate
ifrom0tonums.size() - 1. For each elementnums[i]:- If
nums[i]is NOT0:- We've found a non-zero element! This element belongs in our "Good" section.
- We need to move
nums[i]tonums[nonZeroPtr]. The easiest way to do this while preserving relative order is to swapnums[i]withnums[nonZeroPtr]. - After placing the non-zero element, we increment
nonZeroPtr. This moves our "next available position" for a non-zero element one step forward.
- If
nums[i]IS0:- We simply ignore it for now. It's a zero, and it needs to go to the end. By skipping it, we ensure that
nonZeroPtrdoesn't advance, effectively leaving space for the zero to eventually be overwritten or remain at the end.
- We simply ignore it for now. It's a zero, and it needs to go to the end. By skipping it, we ensure that
- If
After the loop finishes: All non-zero elements will be neatly packed at the beginning of the array (from index
0up tononZeroPtr - 1), maintaining their original relative order. The remaining elements fromnonZeroPtrto the end of the array will all be0s (either original zeroes that were "skipped" or zeroes that were swapped out).
Let's quickly trace [0, 1, 0, 3, 12] with this approach:
i |
nums[i] |
nonZeroPtr |
Array State (nums) |
Action |
|---|---|---|---|---|
| - | - | 0 |
[0, 1, 0, 3, 12] |
Initialize nonZeroPtr
|
| 0 | 0 |
0 |
[0, 1, 0, 3, 12] |
nums[0] is 0. Skip. |
| 1 | 1 |
0 |
[1, 0, 0, 3, 12] |
nums[1] is 1. Swap nums[0] and nums[1]. nonZeroPtr becomes 1. |
| 2 | 0 |
1 |
[1, 0, 0, 3, 12] |
nums[2] is 0. Skip. |
| 3 | 3 |
1 |
[1, 3, 0, 0, 12] |
nums[3] is 3. Swap nums[1] and nums[3]. nonZeroPtr becomes 2. |
| 4 | 12 |
2 |
[1, 3, 12, 0, 0] |
nums[4] is 12. Swap nums[2] and nums[4]. nonZeroPtr becomes 3. |
| End | - | 3 |
[1, 3, 12, 0, 0] |
Loop ends. All non-zeroes moved to front. |
Perfect!
💻 The Code
Here's the C++ implementation of this efficient two-pointer approach:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
// nonZeroPtr tracks the next position to place a non-zero element.
// It essentially marks the boundary between confirmed non-zeroes and the rest.
int nonZeroPtr = 0;
// Iterate through the entire array with 'i' (currentPtr).
for (int i = 0; i < nums.size(); i++) {
// If the element at the current position 'i' is NOT zero:
if (nums[i] != 0) {
// It means we've found a non-zero element that needs to be moved
// to the front part of the array, specifically to the position
// indicated by 'nonZeroPtr'.
// Swap the non-zero element at 'nums[i]' with the element
// at 'nums[nonZeroPtr]'.
// This moves the non-zero element to its correct position
// at the front, while pushing any existing zero at nonZeroPtr's
// position further back.
std::swap(nums[nonZeroPtr], nums[i]);
// After placing the non-zero element, advance nonZeroPtr.
// It's now ready for the next non-zero element we find.
nonZeroPtr++;
}
// If nums[i] IS zero, we simply ignore it and let 'i' advance.
// This '0' will either be swapped out by a non-zero element later,
// or it will naturally remain at the end of the array.
}
}
};
⏱️ Complexity Analysis
Let's break down how efficient our solution is:
-
Time Complexity: O(N)
- We iterate through the array exactly once using the
ipointer. - In the worst-case scenario (e.g.,
[1,2,3,4,5,0,0,0,0,0]), we might perform N swaps, but each element is visited and potentially swapped at most once. - Therefore, the time taken grows linearly with the number of elements
Nin the array.
- We iterate through the array exactly once using the
-
Space Complexity: O(1)
- We are modifying the array in-place. We don't create any new arrays or data structures that scale with the input size.
- We only use a few constant-space variables (
nonZeroPtr,i). - This makes our solution very memory-efficient!
✅ Key Takeaways
- Two-Pointer Power: This problem beautifully illustrates the power and elegance of the two-pointer technique for in-place array manipulation. It's a fundamental pattern you'll use often!
- Flip the Perspective: Sometimes, instead of solving a problem directly, thinking about its inverse (e.g., "move non-zeroes to front" instead of "move zeroes to end") can lead to a simpler, more efficient solution.
- In-Place Optimizations: When memory is a constraint, in-place algorithms are your best friends. They minimize memory usage by modifying existing data structures directly.
- Relative Order Matters: Always pay close attention to constraints like "maintaining relative order." It often dictates the specific variant of an algorithm you need to use.
This problem is a fantastic stepping stone. Master it, and you'll be well-equipped for many other array-based challenges!
Happy Coding! ✨
Submission Details:
Author Account: Vansh2710
Time Published: 2026-03-26 01:00:44
Top comments (0)