Difficulty: Easy, Asked-in: Facebook, Amazon, Uber, LinkedIn, Bloomberg.
Key-takeaway: An excellent problem to learn problem-solving using single loop and two pointers approach. In two-pointers approach, both pointers are moving in the same direction.
Given an array X[] of n integers, where some elements are zero and some elements are non-zero. Write a program to move all the zeroes to the end of the array.
Example 1
Input: X[] = [4, 8, 0, 0, 2, 0, 1, 0], Output: X[] = [4, 8, 2, 1, 0, 0, 0, 0]
Explanation: The zeros are moved to the end of the array while maintaining the order of non-zero elements.
Example 2
Input: X[] = [1, 2, 3, 4, 0, 0, 0], Output: [1, 2, 3, 4, 0, 0, 0]
Explanation: Since the zeros are already at the end of the array, the array remains unchanged.
Example 3
Input: X[] = [0, 0, 1, 2, 0, 3, 4], Output: [1, 2, 3, 4, 0, 0, 0]
One basic idea is to allocate extra memory of size n and traverse the input array to store all the non-zero elements at the starting indexes of the extra memory. After this, we can fill the remaining extra memory space with zeroes.
This solution will preserve the order of elements, but there are two drawbacks:
int* moveZeroesEnd(int X[], int n)
{
int* output = new int[n];
int j = 0;
for (int i = 0; i < n; i = i + 1)
{
if (X[i] != 0)
{
output[j] = X[i];
j = j + 1;
}
}
while (j < n)
{
output[j] = 0;
j = j + 1;
}
return output;
}
def moveZeroesEnd(X):
n = len(X)
output = [0] * n
j = 0
for i, x in enumerate(X):
if x != 0:
output[j] = x
j = j + 1
return output
Time complexity in the worst case = Time complexity of traversing X[] and storing non-zero elements in output[] + Time complexity of storing zeroes in output[] = O(n) + O(n) = O(n).
The worst-case scenario occurs when all array elements are zero (traversal of both arrays) and the best-case scenario occurs when all array elements are non-zero (Single traversal of input array).
Space complexity = O(n), for extra space output[].
The critical question is: Can we further optimize the space complexity of the above solution and solve the problem in place? Let's think!
We will use an idea similar to the above approach, but instead of storing the output in extra space, we will shift the non-zero elements to the start of the input array itself and fill the remaining part with zeroes.
Here are the steps:
This solution will preserve the order of elements, but there is one drawback: we are using two loops to generate the output.
void moveZeroEnd(int X[], int n)
{
int j = 0;
for (int i = 0; i < n; i = i + 1)
{
if (X[i] != 0)
{
X[j] = X[i];
j = j + 1;
}
}
while (j < n)
{
X[j] = 0;
j = j + 1;
}
}
def moveZeroesEnd(X, n):
j = 0
for i in range(n):
if X[i] != 0:
X[j] = X[i]
j = j + 1
while j < len(X):
X[j] = 0
j = j + 1
Time complexity in the worst case = Time complexity of traversing X[] to shift non-zero elements + Time complexity of traversing X[] to fill zeroes = O(n) + O(n) = O(n). We are solving the problem in-place, so space complexity = O(1).
Can we further optimize the above approach and solve it using a single loop? Can we reduce the second traversal to fill the zero at the end? Let's think!
If we observe the intermediate step of the first loop in the above solution, we can observe a pattern:
So, when the element X[i] is non-zero, instead of updating X[j] with X[i], if we swap the element at index i with the element at index j (first zero elements), then we wouldn't need to fill the rest of the array with zeroes at the end. All zero elements will be moved to the end due automatically to the swapping. Think!
Note: This solution idea is similar to the partition process of the quick sort.
void moveZeroEnd(int X[], int n)
{
int j = 0;
for (int i = 0; i < n; i = i + 1)
{
if (X[i] != 0)
{
swap(X[j], X[i]);
j = j + 1;
}
}
}
def moveZeroEnd(X, n):
j = 0
for i in range(n):
if X[i] != 0:
X[i], X[j] = X[j], X[i]
j = j + 1
In the above code, if some non-zero elements are continuously present at the starting indexes, unnecessary swapping will occurs. So, how can we optimize the above code?
Here's an idea: Inside the loop, when X[i] is non-zero, we can check if i and j are not the same. If they are same, we do nothing and move forward. If they are different, we directly assign the non-zero element at index i to X[j] and assign 0 to X[i]. This approach will eliminate unnecessary swapping.
void moveZeroEnd(int X[], int n)
{
int j = 0;
for (int i = 0; i < n; i = i + 1)
{
if (X[i] != 0)
{
if (i != j)
{
X[j] = X[i];
X[i] = 0;
}
j = j + 1;
}
}
}
We are doing a single traversal where comparison is the critical operation. Time complexity = O(n), Space complexity = O(1).
Enjoy learning, Enjoy coding, Enjoy algorithms!