Solution
This question comes under a broad category of "Array Transformation". This category is the meat of tech interviews. Mostly because arrays are such a simple and easy to use data structure. Traversal or representation doesn't require any boilerplate code and most of your code will look like the Pseudocode itself.
The 2 requirements of the question are:

Move all the 0's to the end of array.

All the nonzero elements must retain their original order.
It's good to realize here that both the requirements are mutually exclusive, i.e., you can solve the individual subproblems and then combine them for the final solution.
Approach #1 (Space SubOptimal) [Accepted]
C++
void moveZeroes(vector<int>& nums) { int n = nums.size(); // Count the zeroes int numZeroes = 0; for (int i = 0; i < n; i++) { numZeroes += (nums[i] == 0); } // Make all the nonzero elements retain their original order. vector<int> ans; for (int i = 0; i < n; i++) { if (nums[i] != 0) { ans.push_back(nums[i]); } } // Move all zeroes to the end while (numZeroes) { ans.push_back(0); } // Combine the result for (int i = 0; i < n; i++) { nums[i] = ans[i]; } }
Complexity Analysis
Space Complexity : . Since we are creating the "ans" array to store results.
Time Complexity: . However, the total number of operations are suboptimal. We can achieve the same result in less number of operations.
If asked in an interview, the above solution would be a good start. You can explain the interviewer(not code) the above and build your base for the next Optimal Solution.
Approach #2 (Space Optimal, Operation SubOptimal) [Accepted]
This approach works the same way as above, i.e. , first fulfills one requirement and then another. The catch? It does it in a clever way. The above problem can also be stated in alternate way, " Bring all the non 0 elements to the front of array keeping their relative order same".
This is a 2 pointer approach. The fast pointer which is denoted by variable "cur" does the job of processing new elements. If the newly found element is not a 0, we record it just after the last found non0 element. The position of last found non0 element is denoted by the slow pointer "lastNonZeroFoundAt" variable. As we keep finding new non0 elements, we just overwrite them at the "lastNonZeroFoundAt + 1" 'th index. This overwrite will not result in any loss of data because we already processed what was there(if it were non0,it already is now written at it's corresponding index,or if it were 0 it will be handled later in time).
After the "cur" index reaches the end of array, we now know that all the non0 elements have been moved to beginning of array in their original order. Now comes the time to fulfil other requirement, "Move all 0's to the end". We now simply need to fill all the indexes after the "lastNonZeroFoundAt" index with 0.
C++
void moveZeroes(vector<int>& nums) { int lastNonZeroFoundAt = 0; // If the current element is not 0, then we need to // append it just in front of last non 0 element we found. for (int i = 0; i < nums.size(); i++) { if (nums[i] != 0) { nums[lastNonZeroFoundAt++] = nums[i]; } } // After we have finished processing new elements, // all the nonzero elements are already at beginning of array. // We just need to fill remaining array with 0's. for (int i = lastNonZeroFoundAt; i < nums.size(); i++) { nums[i] = 0; } }
Complexity Analysis
Space Complexity : . Only constant space is used.
Time Complexity: . However, the total number of operations are still suboptimal. The total operations (array writes) that code does is (Total number of elements).
Approach #3 (Optimal) [Accepted]
The total number of operations of the previous approach is suboptimal. For example, the array which has all (except last) leading zeroes: [0, 0, 0, ..., 0, 1].How many write operations to the array? For the previous approach, it writes 0's times, which is not necessary. We could have instead written just once. How? ..... By only fixing the non0 element,i.e., 1.
The optimal approach is again a subtle extension of above solution. A simple realization is if the current element is non0, its' correct position can at best be it's current position or a position earlier. If it's the latter one, the current position will be eventually occupied by a non0 ,or a 0, which lies at a index greater than 'cur' index. We fill the current position by 0 right away,so that unlike the previous solution, we don't need to come back here in next iteration.
In other words, the code will maintain the following invariant:
All elements before the slow pointer (lastNonZeroFoundAt) are nonzeroes.
All elements between the current and slow pointer are zeroes.
Therefore, when we encounter a nonzero element, we need to swap elements pointed by current and slow pointer, then advance both pointers. If it's zero element, we just advance current pointer.
With this invariant inplace, it's easy to see that the algorithm will work.
C++
void moveZeroes(vector<int>& nums) { for (int lastNonZeroFoundAt = 0, cur = 0; cur < nums.size(); cur++) { if (nums[cur] != 0) { swap(nums[lastNonZeroFoundAt++], nums[cur]); } } }
Complexity Analysis
Space Complexity : . Only constant space is used.
Time Complexity: . However, the total number of operations are optimal. The total operations (array writes) that code does is Number of non0 elements.This gives us a much better bestcase (when most of the elements are 0) complexity than last solution. However, the worstcase (when all elements are non0) complexity for both the algorithms is same.
Analysis written by: @spandan.pathak