1337. The K Weakest Rows in a Matrix
Problem Description
You have a binary matrix mat
with dimensions m x n
, where each cell contains either 1
(representing a soldier) or 0
(representing a civilian). In each row, all soldiers (1
s) appear before all civilians (0
s) - meaning all 1
s are positioned to the left of all 0
s.
The task is to rank rows from weakest to strongest based on these rules:
- A row with fewer soldiers is weaker than a row with more soldiers
- If two rows have the same number of soldiers, the row with the smaller index is considered weaker
For example, if row 2 has 2 soldiers and row 5 has 3 soldiers, then row 2 is weaker. If both row 2 and row 4 have 2 soldiers each, then row 2 is weaker because 2 < 4
.
You need to return a list containing the indices of the k
weakest rows, ordered from weakest to strongest.
The solution uses binary search (bisect_right
) to efficiently count soldiers in each row. Since soldiers are at the front (left side), it reverses each row and finds where 0
s end, which gives the soldier count. Then it sorts row indices by their soldier counts (with index as tiebreaker) and returns the first k
indices.
Intuition
The key insight is that we need to sort rows based on two criteria: the number of soldiers (primary) and the row index (secondary). Since all 1
s appear before all 0
s in each row, counting soldiers becomes a simple task of finding where the 1
s end.
We could iterate through each row and count 1
s manually, but there's a more elegant approach. Since the rows are already sorted (all 1
s followed by all 0
s), we can use binary search to find the boundary between soldiers and civilians. This is where bisect_right
comes in handy.
The clever trick in the solution is reversing each row before applying bisect_right(row[::-1], 0)
. Why reverse? When we reverse a row like [1,1,1,0,0]
, it becomes [0,0,1,1,1]
. Now bisect_right
finds the rightmost position where we can insert 0
while maintaining order. This position directly tells us how many 0
s there are in the reversed row, and n - (number of 0s)
gives us the soldier count.
Once we have the soldier count for each row, we need to sort the row indices based on these counts. Python's sort is stable, meaning if two elements compare equal, they maintain their original relative order. By sorting indices [0, 1, 2, ..., m-1]
using soldier counts as the key, rows with the same soldier count automatically maintain their index order, satisfying our tiebreaker rule.
Finally, we just take the first k
elements from our sorted indices to get the k
weakest rows.
Learn more about Binary Search, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows these steps:
-
Extract matrix dimensions: First, we get
m
(number of rows) andn
(number of columns) from the input matrix. -
Count soldiers in each row: We create a list
ans
where each element represents the soldier count for the corresponding row. For each row:- Reverse the row using
row[::-1]
to transform something like[1,1,0,0]
into[0,0,1,1]
- Apply
bisect_right(row[::-1], 0)
to find the rightmost position where0
can be inserted, which gives us the count of civilians - Calculate soldiers as
n - bisect_right(row[::-1], 0)
- Reverse the row using
-
Create and sort row indices:
- Generate a list of indices
idx = list(range(m))
which creates[0, 1, 2, ..., m-1]
- Sort these indices using a custom key:
idx.sort(key=lambda i: ans[i])
- This sorts indices based on their corresponding soldier counts in
ans
- Since Python's sort is stable, rows with equal soldier counts maintain their original order (smaller indices first)
- Generate a list of indices
-
Return k weakest rows: Simply slice the sorted indices list to get the first
k
elements:return idx[:k]
The time complexity is O(m * log n + m * log m)
where:
O(m * log n)
for binary search on each of them
rowsO(m * log m)
for sorting the indices
The space complexity is O(m)
for storing the soldier counts and indices lists.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with a small matrix and k = 3
:
mat = [[1,1,0,0,0], # row 0: 2 soldiers [1,1,1,1,0], # row 1: 4 soldiers [1,0,0,0,0], # row 2: 1 soldier [1,1,1,0,0]] # row 3: 3 soldiers k = 3
Step 1: Extract dimensions
m = 4
(4 rows)n = 5
(5 columns)
Step 2: Count soldiers in each row using binary search
For row 0: [1,1,0,0,0]
- Reverse it:
[0,0,0,1,1]
bisect_right([0,0,0,1,1], 0)
returns3
(position after last 0)- Soldiers =
5 - 3 = 2
For row 1: [1,1,1,1,0]
- Reverse it:
[0,1,1,1,1]
bisect_right([0,1,1,1,1], 0)
returns1
- Soldiers =
5 - 1 = 4
For row 2: [1,0,0,0,0]
- Reverse it:
[0,0,0,0,1]
bisect_right([0,0,0,0,1], 0)
returns4
- Soldiers =
5 - 4 = 1
For row 3: [1,1,1,0,0]
- Reverse it:
[0,0,1,1,1]
bisect_right([0,0,1,1,1], 0)
returns2
- Soldiers =
5 - 2 = 3
So ans = [2, 4, 1, 3]
(soldier counts for rows 0, 1, 2, 3)
Step 3: Sort row indices by soldier count
- Start with
idx = [0, 1, 2, 3]
- Sort by soldier count:
- Row 2 has 1 soldier (weakest)
- Row 0 has 2 soldiers
- Row 3 has 3 soldiers
- Row 1 has 4 soldiers (strongest)
- After sorting:
idx = [2, 0, 3, 1]
Step 4: Return k weakest rows
- Take first 3 elements:
[2, 0, 3]
The answer is [2, 0, 3]
, representing the 3 weakest rows in order from weakest to strongest.
Solution Implementation
1from typing import List
2import bisect
3
4class Solution:
5 def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]:
6 # Get dimensions of the matrix
7 num_rows, num_cols = len(mat), len(mat[0])
8
9 # Calculate soldier count for each row
10 # Since rows have 1s (soldiers) followed by 0s (civilians),
11 # reversing the row and finding rightmost position of 0 gives soldier count
12 soldier_counts = [num_cols - bisect.bisect_right(row[::-1], 0) for row in mat]
13
14 # Create list of row indices
15 row_indices = list(range(num_rows))
16
17 # Sort row indices based on soldier count (weakest to strongest)
18 # In case of tie, smaller index comes first (naturally preserved by stable sort)
19 row_indices.sort(key=lambda i: soldier_counts[i])
20
21 # Return the first k weakest row indices
22 return row_indices[:k]
23
1class Solution {
2 public int[] kWeakestRows(int[][] mat, int k) {
3 int numRows = mat.length;
4 int numCols = mat[0].length;
5
6 // Array to store the number of soldiers (1s) in each row
7 int[] soldierCount = new int[numRows];
8
9 // List to store row indices for sorting
10 List<Integer> rowIndices = new ArrayList<>();
11
12 // Process each row to count soldiers
13 for (int i = 0; i < numRows; i++) {
14 rowIndices.add(i);
15 int[] currentRow = mat[i];
16
17 // Binary search to find the first 0 (end of soldiers)
18 // Since soldiers (1s) appear before civilians (0s)
19 int left = 0;
20 int right = numCols;
21
22 while (left < right) {
23 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
24
25 if (currentRow[mid] == 0) {
26 // Found a 0, search in the left half
27 right = mid;
28 } else {
29 // Found a 1, search in the right half
30 left = mid + 1;
31 }
32 }
33
34 // Store the count of soldiers for this row
35 soldierCount[i] = left;
36 }
37
38 // Sort row indices based on soldier count (weakest to strongest)
39 // If soldier counts are equal, indices are already in ascending order
40 rowIndices.sort(Comparator.comparingInt(index -> soldierCount[index]));
41
42 // Build the result array with k weakest rows
43 int[] result = new int[k];
44 for (int i = 0; i < k; i++) {
45 result[i] = rowIndices.get(i);
46 }
47
48 return result;
49 }
50}
51
1class Solution {
2public:
3 /**
4 * Binary search to find the number of soldiers (1s) in a row
5 * Since soldiers appear before civilians, we find the first 0's position
6 * @param row - vector containing 1s (soldiers) followed by 0s (civilians)
7 * @return - count of soldiers in the row
8 */
9 int search(vector<int>& row) {
10 int left = 0;
11 int right = row.size() - 1;
12
13 // Binary search for the first occurrence of 0
14 while (left <= right) {
15 int mid = left + (right - left) / 2;
16
17 if (row[mid] == 0) {
18 // Move left to find the first 0
19 right = mid - 1;
20 } else {
21 // Current element is 1, search in right half
22 left = mid + 1;
23 }
24 }
25
26 // 'left' now points to the first 0's position (or array length if all 1s)
27 return left;
28 }
29
30 /**
31 * Find k weakest rows in the matrix
32 * A row is weaker if it has fewer soldiers, or same soldiers but smaller index
33 * @param mat - binary matrix where 1 represents soldier and 0 represents civilian
34 * @param k - number of weakest rows to return
35 * @return - indices of k weakest rows
36 */
37 vector<int> kWeakestRows(vector<vector<int>>& mat, int k) {
38 // Store pairs of (soldier count, row index) for sorting
39 vector<pair<int, int>> rowStrengths;
40
41 // Count soldiers in each row
42 for (int rowIndex = 0; rowIndex < mat.size(); rowIndex++) {
43 int soldierCount = search(mat[rowIndex]);
44 rowStrengths.push_back({soldierCount, rowIndex});
45 }
46
47 // Sort by soldier count first, then by row index (automatically handled by pair comparison)
48 sort(rowStrengths.begin(), rowStrengths.end());
49
50 // Extract the indices of k weakest rows
51 vector<int> result;
52 for (int i = 0; i < k; i++) {
53 result.push_back(rowStrengths[i].second);
54 }
55
56 return result;
57 }
58};
59
1/**
2 * Find the k weakest rows in a binary matrix
3 * A row is weaker if it has fewer 1s, or same number of 1s but smaller index
4 * @param mat - Binary matrix where 1s represent soldiers and 0s represent civilians
5 * @param k - Number of weakest rows to return
6 * @returns Array of indices of the k weakest rows
7 */
8function kWeakestRows(mat: number[][], k: number): number[] {
9 const rowCount: number = mat.length;
10
11 // Create array of [soldierCount, originalIndex] pairs for each row
12 const rowStrengthPairs: [number, number][] = mat.map((row, index) => {
13 const soldierCount = row.reduce((sum, value) => sum + value, 0);
14 return [soldierCount, index];
15 });
16
17 const weakestRowIndices: number[] = [];
18
19 // Partial selection sort to find k weakest rows
20 // Only need to sort first k elements
21 for (let i = 0; i < k; i++) {
22 // Find the minimum element from position i to end
23 for (let j = i + 1; j < rowCount; j++) {
24 const currentStrength = rowStrengthPairs[i][0];
25 const currentIndex = rowStrengthPairs[i][1];
26 const candidateStrength = rowStrengthPairs[j][0];
27 const candidateIndex = rowStrengthPairs[j][1];
28
29 // Check if candidate row is weaker than current row
30 const isCandidateWeaker = candidateStrength < currentStrength ||
31 (candidateStrength === currentStrength && candidateIndex < currentIndex);
32
33 if (isCandidateWeaker) {
34 // Swap the weaker row to position i
35 [rowStrengthPairs[i], rowStrengthPairs[j]] = [rowStrengthPairs[j], rowStrengthPairs[i]];
36 }
37 }
38
39 // Add the index of the i-th weakest row to result
40 weakestRowIndices.push(rowStrengthPairs[i][1]);
41 }
42
43 return weakestRowIndices;
44}
45
Time and Space Complexity
Time Complexity: O(m * n + m * log(m))
- Creating the
ans
list requires iterating through each row and for each row:- Reversing the row takes
O(n)
time bisect_right
on the reversed row takesO(log n)
time- Overall for all
m
rows:O(m * n)
(sincen > log n
)
- Reversing the row takes
- Creating the
idx
list takesO(m)
time - Sorting the
idx
list based on the values inans
takesO(m * log m)
time - Slicing to get the first
k
elements takesO(k)
time
Total: O(m * n + m * log m)
where m
is the number of rows and n
is the number of columns
Space Complexity: O(m)
- The
ans
list storesm
integers:O(m)
space - The
idx
list storesm
indices:O(m)
space - The reversed row
row[::-1]
creates a temporary copy:O(n)
space (but this is reused for each row, not cumulative) - The sorting operation may use
O(log m)
space for the recursion stack (in case of quicksort/mergesort) - The output slice
idx[:k]
takesO(k)
space
Total auxiliary space (excluding output): O(m + n)
, which simplifies to O(m)
when m ≥ n
or O(n)
when n > m
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Inefficient Linear Search Instead of Binary Search
A common mistake is using a simple loop to count soldiers, which defeats the purpose of having a sorted row structure:
Incorrect approach:
# Inefficient O(n) search per row
soldier_counts = [sum(row) for row in mat] # Works but inefficient
# or
soldier_counts = [row.count(1) for row in mat] # Also O(n) per row
Why it's problematic: Since each row is already sorted (all 1s before 0s), we can leverage binary search for O(log n) complexity per row instead of O(n).
Correct approach:
# Use binary search to find the boundary between 1s and 0s soldier_counts = [bisect.bisect_right(row, 0, key=lambda x: -x) for row in mat] # or reverse and search as in the original solution soldier_counts = [num_cols - bisect.bisect_right(row[::-1], 0) for row in mat]
2. Incorrect Handling of Tie-Breaking
When multiple rows have the same soldier count, the row with the smaller index should be considered weaker. A pitfall is not preserving this ordering:
Incorrect approach:
# This might not preserve index order for ties
combined = [(soldier_counts[i], i) for i in range(num_rows)]
combined.sort() # Correct, but unnecessarily complex
# Or worse, losing the original indices entirely:
soldier_counts.sort() # This loses row index information!
Correct approach:
# Python's sort is stable, so this naturally preserves index order for ties row_indices.sort(key=lambda i: soldier_counts[i]) # Or explicitly include index in sort key (though unnecessary): row_indices.sort(key=lambda i: (soldier_counts[i], i))
3. Memory Inefficiency with Row Reversal
The original solution creates a reversed copy of each row with row[::-1]
, which uses extra memory:
Memory-intensive approach:
soldier_counts = [num_cols - bisect.bisect_right(row[::-1], 0) for row in mat]
More memory-efficient approach:
# Search for the first 0 directly without reversing soldier_counts = [] for row in mat: # Find leftmost position of 0 (end of 1s) left_pos = bisect.bisect_left(row, 0, key=lambda x: -x) soldier_counts.append(left_pos)
4. Edge Case: All Soldiers or All Civilians
Failing to handle rows that contain only 1s or only 0s:
Potential issue:
# If a row is all 1s: [1,1,1,1] # Reversed: [1,1,1,1] # bisect_right on 0 returns 0, giving correct count of num_cols - 0 = 4 # If a row is all 0s: [0,0,0,0] # Reversed: [0,0,0,0] # bisect_right on 0 returns 4, giving correct count of num_cols - 4 = 0
The original solution handles these correctly, but custom implementations might fail if not careful about boundary conditions.
Which of the following array represent a max heap?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!