Facebook Pixel

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 (bisect_right) to efficiently count soldiers in each row. Since soldiers are at the front (left side), it reverses each row and finds where 0s 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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, but there's a more elegant approach. Since the rows are already sorted (all 1s followed by all 0s), 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 0s 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:

  1. Extract matrix dimensions: First, we get m (number of rows) and n (number of columns) from the input matrix.

  2. 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 where 0 can be inserted, which gives us the count of civilians
    • Calculate soldiers as n - bisect_right(row[::-1], 0)
  3. 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)
  4. 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:

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 Evaluator

Example 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) returns 3 (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) returns 1
  • 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) returns 4
  • 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) returns 2
  • 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 takes O(log n) time
    • Overall for all m rows: O(m * n) (since n > log n)
  • Creating the idx list takes O(m) time
  • Sorting the idx list based on the values in ans takes O(m * log m) time
  • Slicing to get the first k elements takes O(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 stores m integers: O(m) space
  • The idx list stores m 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] takes O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More