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 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.

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 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]
41
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            // 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}
54
1class 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};
62
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 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}
56

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 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_index

    The feasible condition is row[mid] == 0 (found a civilian). When we find a 0, we record its position and search left for an earlier 0. When we find a 1, we search right. If no 0 is found (first_zero_index == -1), all elements are soldiers, so we return num_cols.

  3. 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)
  4. Return k weakest rows: Simply slice the sorted indices list to get the first k elements.

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.

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. 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.

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

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

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

Load More