Minimum Swaps to Arrange a Binary Grid
MedPrompt
Given an n x n binary grid, in one step you can choose two adjacent rows of the grid and swap them.
A grid is said to be valid if all the cells above the main diagonal are zeros.
Return the minimum number of steps needed to make the grid valid, or -1 if the grid cannot be valid.
The main diagonal of a grid is the diagonal that starts at cell (1, 1) and ends at cell (n, n).
Example 1:
Input: grid = [[0,0,1],[1,1,0],[1,0,0]]
Output: 3Example 2:
Input: grid = [[0,1,1,0],[0,1,1,0],[0,1,1,0],[0,1,1,0]]
Output: -1
Explanation: All rows are similar, swaps have no effect on the grid.Example 3:
Input: grid = [[1,0,0],[1,1,0],[1,1,1]]
Output: 0
Constraints:
n == grid.length== grid[i].length1 <= n <= 200grid[i][j]is either0or1
Approaches
2 approaches with complexity analysis and trade-offs.
This approach exhaustively checks every possible arrangement of rows to find a valid one that requires the minimum number of swaps. It's conceptually simple but computationally very expensive.
Algorithm
-
- Pre-calculate and store the number of trailing zeros for each of the original
nrows.
- Pre-calculate and store the number of trailing zeros for each of the original
-
- Initialize a variable
min_swapsto a very large value.
- Initialize a variable
-
- Generate every permutation of row indices
[0, 1, ..., n-1].
- Generate every permutation of row indices
-
- For each permutation
p:
- a. Check Validity: Verify if the arrangement is valid. For each position
i, the rowp[i]must have at leastn - 1 - itrailing zeros. - b. Calculate Swaps: If the permutation is valid, calculate the number of inversions in
p. This gives the number of adjacent swaps. - c. Update Minimum: Update
min_swaps = min(min_swaps, number_of_inversions).
- For each permutation
-
- If
min_swapsremains at its initial large value, no solution exists; return -1. Otherwise, returnmin_swaps.
- If
Walkthrough
The fundamental idea is to generate all n! permutations of the grid's rows. For each permutation, we first check if it meets the criteria for a 'valid' grid (all cells above the main diagonal are zero). If it is valid, we then calculate the number of adjacent swaps needed to transform the original row order into this new, valid order. This number of swaps is equivalent to the number of inversions in the permutation. We keep track of the minimum swap count found across all valid permutations.
This method is guaranteed to find the optimal solution because it explores the entire solution space. However, due to the factorial growth (n!), it is not feasible for the constraints given in the problem (n <= 200) and will time out.
Complexity
Time
O(n! * n^2) - Generating `n!` permutations, and for each, performing a check and inversion count which takes at least `O(n)` time.
Space
O(n^2) - To store the grid itself. Additional `O(n)` space is needed for storing permutations and helper arrays.
Trade-offs
Pros
Guarantees finding the absolute minimum number of swaps if a solution exists.
Cons
Extremely inefficient with a time complexity of
O(n! * n^2), making it impractical forn > 10-12.
A much more efficient approach is to solve the problem greedily. We first simplify the problem by representing each row by its count of trailing zeros. Then, we iterate from the top row downwards, and for each position, we find the closest available row that satisfies the requirement and move it into place, summing the swaps.
Algorithm
-
- Create a list
trailing_zerosof sizen.
- Create a list
-
- For each row
ifrom0ton-1, count its trailing zeros and store it intrailing_zeros[i].
- For each row
-
- Initialize
swaps = 0.
- Initialize
-
- Iterate
ifrom0ton-1(the target row position):
- a. Calculate
required_zeros = n - 1 - i. - b. Search for the first index
j >= iwheretrailing_zeros[j] >= required_zeros. - c. If no such
jexists, return -1. - d. Add
j - itoswaps. - e. Simulate the swap by moving the element at
jto positioniin thetrailing_zeroslist.
- Iterate
-
- Return
swaps.
- Return
Walkthrough
The condition for a valid grid implies that for each row i (from 0 to n-1), the row placed at this position must have at least n - 1 - i trailing zeros. This insight allows us to build the valid grid row by row.
The algorithm proceeds as follows:
- Preprocessing: First, we iterate through the input
gridand calculate the number of trailing zeros for each row. We store these counts in a list, saytrailing_zeros, which maintains the current order of rows. - Greedy Swapping: We then iterate from
i = 0ton-1. In each iterationi, we are trying to fill thei-th row of the grid. We need a row with at leastn - 1 - itrailing zeros.- We search for the first available row from the current position
ionwards (i.e., intrailing_zeros[i:]) that meets this requirement. - If no such row is found, it's impossible to form a valid grid, and we return -1.
- If we find a suitable row at index
j(j >= i), we know it takesj - iadjacent swaps to move this row to positioni. We add this to our total swap count. - We then update our
trailing_zeroslist to reflect this swap: we move the element from indexjto indexi. This ensures that in the next iteration fori+1, we are working with the correctly updated arrangement of rows.
- We search for the first available row from the current position
This greedy choice is optimal because by picking the closest suitable row, we satisfy the current requirement with the minimum possible number of swaps, without negatively affecting our ability to solve the subproblem for the remaining rows.
import java.util.ArrayList;import java.util.List; class Solution { public int minSwaps(int[][] grid) { int n = grid.length; List<Integer> trailingZeros = new ArrayList<>(); for (int i = 0; i < n; i++) { int count = 0; for (int j = n - 1; j >= 0; j--) { if (grid[i][j] == 0) { count++; } else { break; } } trailingZeros.add(count); } int swaps = 0; for (int i = 0; i < n; i++) { int required = n - 1 - i; int foundIdx = -1; // Find the first suitable row from current position i for (int j = i; j < n; j++) { if (trailingZeros.get(j) >= required) { foundIdx = j; break; } } if (foundIdx == -1) { return -1; // No suitable row found } // Move the found row to position i int val = trailingZeros.remove(foundIdx); trailingZeros.add(i, val); swaps += foundIdx - i; } return swaps; }}Complexity
Time
O(n^2) - The initial calculation of trailing zeros is `O(n^2)`. The main loop runs `n` times, and inside it, the search and list manipulation each take up to `O(n)` time, leading to `O(n^2)` for the swapping part.
Space
O(n) - To store the list of trailing zero counts.
Trade-offs
Pros
Efficient with
O(n^2)time complexity, which is well within limits.The greedy logic is intuitive and relatively simple to implement.
Cons
The use of a list with removals and insertions can be slightly less performant than pure array manipulations in some languages, but the overall complexity remains the same.
Solutions
Solution
class Solution {public int minSwaps(int[][] grid) { int n = grid.length; int[] pos = new int[n]; Arrays.fill(pos, -1); for (int i = 0; i < n; ++i) { for (int j = n - 1; j >= 0; --j) { if (grid[i][j] == 1) { pos[i] = j; break; } } } int ans = 0; for (int i = 0; i < n; ++i) { int k = -1; for (int j = i; j < n; ++j) { if (pos[j] <= i) { ans += j - i; k = j; break; } } if (k == -1) { return -1; } for (; k > i; --k) { int t = pos[k]; pos[k] = pos[k - 1]; pos[k - 1] = t; } } return ans; }}class Solution: def minSwaps(self, grid: List[List[int]]) -> int: n = len(grid) pos = [- 1] * n for i in range(n): for j in range(n - 1, - 1, - 1): if grid[i][j] == 1: pos[i] = j break ans = 0 for i in range(n): k = - 1 for j in range(i, n): if pos[j] <= i: ans += j - i k = j break if k == - 1: return - 1 while k > i: pos[k], pos[k - 1] = pos[k - 1], pos[k] k -= 1 return ansclass Solution {public: int minSwaps(vector<vector<int>> &grid) { int n = grid.size(); vector<int> pos(n, -1); for (int i = 0; i < n; ++i) { for (int j = n - 1; j >= 0; --j) { if (grid[i][j] == 1) { pos[i] = j; break; } } } int ans = 0; for (int i = 0; i < n; ++i) { int k = -1; for (int j = i; j < n; ++j) { if (pos[j] <= i) { ans += j - i; k = j; break; } } if (k == -1) { return -1; } for (; k > i; --k) { swap(pos[k], pos[k - 1]); } } return ans; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.