LeetCode 3936 - Minimum Swaps to Move Zeros to End
The problem gives us an integer array nums containing values between 0 and 100. We may perform an operation where we choose any two distinct indices and swap their values. Our goal is to move all zeros to the end of the array while using the minimum possible number of swaps.
Difficulty: 🟢 Easy
Topics: —
Solution
LeetCode 3936 - Minimum Swaps to Move Zeros to End
Problem Understanding
The problem gives us an integer array nums containing values between 0 and 100. We may perform an operation where we choose any two distinct indices and swap their values. Our goal is to move all zeros to the end of the array while using the minimum possible number of swaps.
An important detail is that the relative order of the non-zero elements does not matter. The problem only requires that every zero ends up in the suffix of the array. Any arrangement satisfying this condition is acceptable.
To understand the target configuration, suppose the array contains k zeros. Then the last k positions of the array must contain all zeros, and the first n - k positions must contain only non-zero values.
The constraints are very small, with n ≤ 100, but the challenge is not about handling large inputs. Instead, it is about finding the minimum number of swaps.
Several edge cases are worth noting:
- Arrays with no zeros already satisfy the requirement, so the answer is
0. - Arrays consisting entirely of zeros also require
0swaps. - Some zeros may already be located in their final positions.
- A single swap can simultaneously place a misplaced zero into the suffix and a misplaced non-zero into the prefix, so we should count swaps carefully.
- Since any two positions can be swapped, we are not restricted to adjacent swaps.
Approaches
Brute Force Approach
A brute force solution would attempt to explore all possible swap sequences until reaching a valid configuration. Since each operation can swap any pair of positions, the branching factor is extremely large.
One could model the problem as a graph where each array state is a node and each swap produces an edge. A breadth-first search would eventually find the minimum number of swaps because BFS explores states in order of increasing number of operations.
This approach is correct because BFS guarantees the shortest path to a valid state. However, it is completely impractical. Even for small arrays, the number of possible permutations grows factorially, making the state space enormous.
Key Observation
Let:
zbe the number of zeros in the array.- The last
zpositions be the positions where zeros must eventually reside.
Consider the first n - z positions, which should contain only non-zero values in the final configuration.
Every zero currently located in this prefix is misplaced. Likewise, every non-zero currently located in the last z positions is misplaced.
Each swap can fix exactly one misplaced zero in the prefix by exchanging it with one misplaced non-zero in the suffix.
Therefore, the minimum number of swaps is simply:
- Count how many zeros appear in the first
n - zpositions.
Each such zero requires one swap, and every swap can correct exactly one of them.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential | Exponential | Explores swap states until reaching a valid arrangement |
| Optimal | O(n) | O(1) | Counts misplaced zeros in the prefix that should contain only non-zero values |
Algorithm Walkthrough
Optimal Algorithm
- Compute the total number of zeros in the array, call it
zero_count. - Determine the size of the prefix that should contain only non-zero values. This size is
n - zero_count. - Traverse the first
n - zero_countpositions. - For every position in this prefix, check whether the value is
0. - If a zero is found, increment the answer because that zero is misplaced and must eventually be swapped into the suffix.
- Return the total count.
Why it works
Let the number of zeros in the array be z. The final z positions must contain all zeros. Therefore, any zero appearing before those positions is misplaced.
Every misplaced zero in the prefix corresponds to a misplaced non-zero in the suffix because the total number of zeros is fixed. A single swap between those two misplaced elements fixes both positions simultaneously.
Thus, each misplaced zero requires exactly one swap, and every swap can eliminate exactly one misplaced zero. Therefore, the number of misplaced zeros in the prefix is precisely the minimum number of swaps required.
Python Solution
class Solution:
def minimumSwaps(self, nums: list[int]) -> int:
zero_count = nums.count(0)
prefix_length = len(nums) - zero_count
swaps = 0
for i in range(prefix_length):
if nums[i] == 0:
swaps += 1
return swaps
The implementation begins by counting the total number of zeros. This immediately tells us how many positions at the end of the array must ultimately contain zeros.
The variable prefix_length represents the portion of the array that should contain only non-zero values in the final arrangement.
We then scan only this prefix. Every zero found there is misplaced and contributes exactly one required swap. The variable swaps accumulates this count.
Finally, the accumulated count is returned as the answer.
Go Solution
func minimumSwaps(nums []int) int {
zeroCount := 0
for _, num := range nums {
if num == 0 {
zeroCount++
}
}
prefixLength := len(nums) - zeroCount
swaps := 0
for i := 0; i < prefixLength; i++ {
if nums[i] == 0 {
swaps++
}
}
return swaps
}
The Go implementation follows exactly the same logic as the Python version. First, it counts the zeros, then computes the size of the prefix that should contain only non-zero values, and finally counts how many zeros appear inside that prefix.
There are no special concerns regarding integer overflow because the array length is at most 100. Go slices are used directly, and no additional data structures are required.
Worked Examples
Example 1
Input
nums = [0,1,0,3,12]
Total zeros:
zero_count = 2
Required prefix length:
5 - 2 = 3
Inspect the first three positions:
| Index | Value | Misplaced Zero? | Swaps |
|---|---|---|---|
| 0 | 0 | Yes | 1 |
| 1 | 1 | No | 1 |
| 2 | 0 | Yes | 2 |
Final answer:
2
Example 2
Input
nums = [0,1,0,2]
Total zeros:
zero_count = 2
Required prefix length:
4 - 2 = 2
Inspect the first two positions:
| Index | Value | Misplaced Zero? | Swaps |
|---|---|---|---|
| 0 | 0 | Yes | 1 |
| 1 | 1 | No | 1 |
Final answer:
1
Example 3
Input
nums = [1,2,0]
Total zeros:
zero_count = 1
Required prefix length:
3 - 1 = 2
Inspect the first two positions:
| Index | Value | Misplaced Zero? | Swaps |
|---|---|---|---|
| 0 | 1 | No | 0 |
| 1 | 2 | No | 0 |
Final answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | One pass to count zeros and one pass over the prefix |
| Space | O(1) | Only a few integer variables are used |
The algorithm performs a constant number of linear scans through the array. No auxiliary data structures proportional to the input size are allocated, so the space usage remains constant.
Test Cases
sol = Solution()
assert sol.minimumSwaps([0, 1, 0, 3, 12]) == 2 # Example 1
assert sol.minimumSwaps([0, 1, 0, 2]) == 1 # Example 2
assert sol.minimumSwaps([1, 2, 0]) == 0 # Example 3
assert sol.minimumSwaps([0]) == 0 # Single zero
assert sol.minimumSwaps([5]) == 0 # Single non-zero
assert sol.minimumSwaps([0, 0, 0]) == 0 # All zeros
assert sol.minimumSwaps([1, 2, 3]) == 0 # No zeros
assert sol.minimumSwaps([0, 1]) == 1 # One swap needed
assert sol.minimumSwaps([1, 0]) == 0 # Already valid
assert sol.minimumSwaps([0, 0, 1, 2]) == 2 # Two misplaced zeros
assert sol.minimumSwaps([1, 2, 0, 0]) == 0 # Zeros already at end
assert sol.minimumSwaps([0, 1, 2, 0, 3, 4]) == 1 # One misplaced zero
assert sol.minimumSwaps([0, 1, 0, 2, 0, 3]) == 2 # Multiple misplaced zeros
assert sol.minimumSwaps([0, 0, 1, 0, 2, 3]) == 2 # Mixed placement
assert sol.minimumSwaps([4, 0, 5, 0, 6, 0]) == 1 # Only one misplaced zero
Test Summary
| Test | Why |
|---|---|
[0,1,0,3,12] |
Official example with multiple swaps |
[0,1,0,2] |
Official example with one swap |
[1,2,0] |
Official example already valid |
[0] |
Smallest array containing zero |
[5] |
Smallest array containing non-zero |
[0,0,0] |
All elements are zero |
[1,2,3] |
No zeros present |
[0,1] |
Minimal non-trivial swap case |
[1,0] |
Zero already in correct position |
[0,0,1,2] |
Multiple misplaced zeros |
[1,2,0,0] |
All zeros already at end |
[0,1,2,0,3,4] |
Exactly one misplaced zero |
[0,1,0,2,0,3] |
Several misplaced zeros |
[0,0,1,0,2,3] |
Mixed distribution |
[4,0,5,0,6,0] |
Tests counting logic carefully |
Edge Cases
All Elements Are Zero
Consider an array such as [0,0,0,0]. Every position already belongs to the final zero suffix because the entire array consists of zeros. A buggy implementation might incorrectly count these zeros as misplaced.
The algorithm handles this correctly because prefix_length = n - zero_count = 0, so the prefix scan examines no elements and returns 0.
No Zeros Exist
For an array like [1,2,3,4], there is nothing to move. The array already satisfies the condition.
The algorithm computes zero_count = 0, making the prefix equal to the entire array. Since there are no zeros in that prefix, the answer remains 0.
Zeros Already at the End
Consider [5,7,0,0]. Although zeros exist, they are already located in the required suffix positions.
The algorithm counts the zeros and inspects only the prefix [5,7]. Since no zeros appear there, the answer is correctly reported as 0.
Alternating Zeros and Non-Zeros
Arrays such as [0,1,0,2,0,3] can be deceptive because zeros are scattered throughout the array.
The algorithm does not simulate swaps. Instead, it directly counts misplaced zeros in the portion that should contain only non-zero values. This guarantees the minimum number of swaps without needing to construct the final arrangement explicitly.