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 (1s) appear before all civilians (0s) - meaning all 1s are positioned to the left of all 0s.
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 to efficiently count soldiers in each row. Since each row has all 1s (soldiers) before all 0s (civilians), binary search finds the first 0 position, which equals 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 1s appear before all 0s in each row, counting soldiers becomes a simple task of finding where the 1s end.
We could iterate through each row and count 1s manually with O(n) complexity, but there's a more efficient approach. Since the rows are already sorted (all 1s followed by all 0s), we can use binary search to find the first 0 in O(log n) time.
The binary search problem can be framed as: "Find the first position where the value is 0." This creates a boolean pattern where all 1s (soldiers) are on the left and all 0s (civilians) are on the right:
Values: [1, 1, 1, 0, 0] Is 0?: [F, F, F, T, T]
We're looking for the first True position, which is the classic boundary-finding binary search pattern. The index of the first 0 equals the soldier count.
Once we have the soldier count for each row, we 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 Implementation
1from typing import List
2
3class Solution:
4 def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]:
5 num_rows, num_cols = len(mat), len(mat[0])
6
7 def count_soldiers(row: List[int]) -> int:
8 """
9 Use binary search to find the first 0 (end of soldiers).
10 Returns the count of soldiers in the row.
11 """
12 left, right = 0, num_cols - 1
13 first_zero_index = -1
14
15 while left <= right:
16 mid = (left + right) // 2
17 if row[mid] == 0:
18 # Found a 0, record it and search left for earlier 0
19 first_zero_index = mid
20 right = mid - 1
21 else:
22 # Found a 1, search in the right half
23 left = mid + 1
24
25 # If no 0 found, all elements are soldiers
26 # Otherwise, first 0's index equals soldier count
27 return num_cols if first_zero_index == -1 else first_zero_index
28
29 # Calculate soldier count for each row using binary search
30 soldier_counts = [count_soldiers(row) for row in mat]
31
32 # Create list of row indices
33 row_indices = list(range(num_rows))
34
35 # Sort row indices based on soldier count (weakest to strongest)
36 # In case of tie, smaller index comes first (naturally preserved by stable sort)
37 row_indices.sort(key=lambda i: soldier_counts[i])
38
39 # Return the first k weakest row indices
40 return row_indices[:k]
411class 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 // Using the canonical binary search template
19 int left = 0;
20 int right = numCols - 1;
21 int firstZeroIndex = -1;
22
23 while (left <= right) {
24 int mid = left + (right - left) / 2;
25
26 if (currentRow[mid] == 0) {
27 // Found a 0, record it and search left for earlier 0
28 firstZeroIndex = mid;
29 right = mid - 1;
30 } else {
31 // Found a 1, search in the right half
32 left = mid + 1;
33 }
34 }
35
36 // If no 0 found, all elements are soldiers (count = numCols)
37 // Otherwise, first 0's index equals soldier count
38 soldierCount[i] = (firstZeroIndex == -1) ? numCols : firstZeroIndex;
39 }
40
41 // Sort row indices based on soldier count (weakest to strongest)
42 // If soldier counts are equal, indices are already in ascending order
43 rowIndices.sort(Comparator.comparingInt(index -> soldierCount[index]));
44
45 // Build the result array with k weakest rows
46 int[] result = new int[k];
47 for (int i = 0; i < k; i++) {
48 result[i] = rowIndices.get(i);
49 }
50
51 return result;
52 }
53}
541class Solution {
2public:
3 /**
4 * Binary search to find the number of soldiers (1s) in a row
5 * Uses the canonical binary search template to find 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 int firstZeroIndex = -1;
13
14 // Binary search for the first occurrence of 0
15 while (left <= right) {
16 int mid = left + (right - left) / 2;
17
18 if (row[mid] == 0) {
19 // Found a 0, record it and search left for earlier 0
20 firstZeroIndex = mid;
21 right = mid - 1;
22 } else {
23 // Current element is 1, search in right half
24 left = mid + 1;
25 }
26 }
27
28 // If no 0 found, all elements are soldiers
29 // Otherwise, first 0's index equals soldier count
30 return (firstZeroIndex == -1) ? row.size() : firstZeroIndex;
31 }
32
33 /**
34 * Find k weakest rows in the matrix
35 * A row is weaker if it has fewer soldiers, or same soldiers but smaller index
36 * @param mat - binary matrix where 1 represents soldier and 0 represents civilian
37 * @param k - number of weakest rows to return
38 * @return - indices of k weakest rows
39 */
40 vector<int> kWeakestRows(vector<vector<int>>& mat, int k) {
41 // Store pairs of (soldier count, row index) for sorting
42 vector<pair<int, int>> rowStrengths;
43
44 // Count soldiers in each row
45 for (int rowIndex = 0; rowIndex < mat.size(); rowIndex++) {
46 int soldierCount = search(mat[rowIndex]);
47 rowStrengths.push_back({soldierCount, rowIndex});
48 }
49
50 // Sort by soldier count first, then by row index (automatically handled by pair comparison)
51 sort(rowStrengths.begin(), rowStrengths.end());
52
53 // Extract the indices of k weakest rows
54 vector<int> result;
55 for (int i = 0; i < k; i++) {
56 result.push_back(rowStrengths[i].second);
57 }
58
59 return result;
60 }
61};
621/**
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 numRows = mat.length;
10 const numCols = mat[0].length;
11
12 /**
13 * Use binary search to count soldiers in a row.
14 * Finds the first 0 (end of soldiers) using the canonical template.
15 */
16 function countSoldiers(row: number[]): number {
17 let left = 0;
18 let right = numCols - 1;
19 let firstZeroIndex = -1;
20
21 while (left <= right) {
22 const mid = Math.floor((left + right) / 2);
23 if (row[mid] === 0) {
24 // Found a 0, record it and search left for earlier 0
25 firstZeroIndex = mid;
26 right = mid - 1;
27 } else {
28 // Found a 1, search in the right half
29 left = mid + 1;
30 }
31 }
32
33 // If no 0 found, all elements are soldiers
34 // Otherwise, first 0's index equals soldier count
35 return firstZeroIndex === -1 ? numCols : firstZeroIndex;
36 }
37
38 // Calculate soldier count for each row using binary search
39 const soldierCounts = mat.map(row => countSoldiers(row));
40
41 // Create array of row indices
42 const rowIndices = Array.from({ length: numRows }, (_, i) => i);
43
44 // Sort row indices based on soldier count (weakest to strongest)
45 // In case of tie, smaller index comes first
46 rowIndices.sort((a, b) => {
47 if (soldierCounts[a] !== soldierCounts[b]) {
48 return soldierCounts[a] - soldierCounts[b];
49 }
50 return a - b;
51 });
52
53 // Return the first k weakest row indices
54 return rowIndices.slice(0, k);
55}
56Solution 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 using the canonical binary search template: For each row, we find the first
0(which gives us the soldier count):left, right = 0, num_cols - 1 first_zero_index = -1 while left <= right: mid = (left + right) // 2 if row[mid] == 0: first_zero_index = mid right = mid - 1 else: left = mid + 1 return num_cols if first_zero_index == -1 else first_zero_indexThe feasible condition is
row[mid] == 0(found a civilian). When we find a0, we record its position and search left for an earlier0. When we find a1, we search right. If no0is found (first_zero_index == -1), all elements are soldiers, so we returnnum_cols. -
Create and sort row indices:
- Generate a list of indices
[0, 1, 2, ..., m-1] - Sort these indices based on their corresponding soldier counts
- 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
kelements.
The time complexity is O(m * log n + m * log m) where:
O(m * log n)for binary search on each of themrowsO(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.
Time and Space Complexity
Time Complexity: O(m * n + m * log(m))
- Creating the
anslist requires iterating through each row and for each row:- Reversing the row takes
O(n)time bisect_righton the reversed row takesO(log n)time- Overall for all
mrows:O(m * n)(sincen > log n)
- Reversing the row takes
- Creating the
idxlist takesO(m)time - Sorting the
idxlist based on the values inanstakesO(m * log m)time - Slicing to get the first
kelements 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
anslist storesmintegers:O(m)space - The
idxlist storesmindices: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. Using Wrong Binary Search Template Variant
A common mistake is using a different binary search variant that doesn't match the canonical template:
Incorrect approach:
# Using bisect functions instead of explicit binary search soldier_counts = [bisect.bisect_right(row[::-1], 0) for row in mat] # Using while left < right with right = mid left, right = 0, n while left < right: mid = (left + right) // 2 if row[mid] == 0: right = mid # Wrong! Should be right = mid - 1 else: left = mid + 1 return left # Wrong! Should track first_zero_index
Why it's problematic: Different variants have different behaviors and are harder to reason about consistently. The canonical template with first_zero_index = -1, while left <= right, and right = mid - 1 provides a consistent pattern.
Correct approach:
left, right = 0, num_cols - 1 first_zero_index = -1 while left <= right: mid = (left + right) // 2 if row[mid] == 0: first_zero_index = mid right = mid - 1 else: left = mid + 1 return num_cols if first_zero_index == -1 else first_zero_index
2. 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 use binary search for O(log n) complexity per row instead of O(n).
3. 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:
# 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: row_indices.sort(key=lambda i: (soldier_counts[i], i))
4. Edge Case: All Soldiers or All Civilians
Failing to handle rows that contain only 1s or only 0s:
# If a row is all 1s: [1,1,1,1] # Binary search never finds a 0, so first_zero_index stays -1 # We must return num_cols (all are soldiers) # If a row is all 0s: [0,0,0,0] # Binary search finds 0 at index 0 # We return first_zero_index = 0 (no soldiers)
The template handles these correctly by checking first_zero_index == -1 after the loop.
Which two pointer techniques do you use to check if a string is a palindrome?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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!