LC1337. The K Weakest Rows in a Matrix¶
Problem Description¶
LeetCode Problem 1337:
You are given an m x n
binary matrix mat
of 1
's (representing soldiers) and 0
's
(representing civilians). The soldiers are positioned in front of the civilians.
That is, all the 1
's will appear to the left of all the 0
's in each row.
A row i
is weaker than a row j
if one of the following is true:
- The number of soldiers in row
i
is less than the number of soldiers in rowj
. - Both rows have the same number of soldiers and
i < j
.
Return the indices of the k
weakest rows in the matrix ordered from weakest to strongest.
Clarification¶
- Binary matrix
- All the 1's appear to the left of all the 0's (how to use it?)
Assumption¶
-
Solution¶
Approach - Heap¶
The problem can be solved using max queue to store top k weakest rows. The priority are
determined by two numbers (number of soldiers, row_id)
. Note that when using tuple
in
python heapq
, it will compare the first elements of tuples. If they are equal, it will
compare the second elements to break the tie. If the comparison is customized, we can
create a class with __lt__
method.
class Item:
def __init__(self, value, row_id):
self.value = value
self.row_id = row_id
def __lt__(self, other):
# Compare by value first, then by row id if values are equal
return (self.value, self.row_id) < (other.value, other.row_id)
import heapq
class Solution:
def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]:
max_heap = []
for i_row, row in enumerate(mat):
n_soldiers = row.count(1) # (1)
heapq.heappush(max_heap, Item(-n_soldiers, -i_row)) # (2)
if len(max_heap) > k:
heapq.heappop(max_heap)
# Essentially, pop items from "max heap".
# (3)
indices = [-heapq.heappop(max_heap)[1] for i in range(len(max_heap))]
return indices[::-1] # (4)
- Could use binary search to find the index of first
0
and the count isindex + 1
based on "all the1
's will appear to the left of all the0
's in each row". - Store negative numbers to achieve max heap.
- Pop negative numbers from small to large (i.e., positive numbers from high to low).
- Reverse the order since it is sorted from largest to smallest.
Complexity Analysis of Approach 1¶
- Time complexity: \(O(m n + m\log k)\) or \(O(m \log n + m \log k)\)
- Iterate all \(m\) rows. Each iteration will
- count number of
1
s which takes \(O(n)\). This could be reduced to \(O(\log n)\) if using binary search. - conduct at most two heap operations which takes \(O(\log k)\) sine the heap size is maintained at \(k\).
- So iterating all rows take \(O(m n + m \log k)\) or \(O(m \log n + m \log k)\) using binary search.
- count number of
- Popping all items from the heap takes \(O(k)\).
- Reversing the final result of \(k\) items takes \(O(k)\).
- So the total time complexity is \(O(m n + m \log k) + O(k) + O(k) = O(m n + m \log k)\) or \(O(m \log n + m \log k)\) using binary search.
- Iterate all \(m\) rows. Each iteration will
- Space complexity: \(O(k)\)
The heap size is \(k\), taking \(O(k)\) space. The final result list takes \(O(k)\) space. So the total space complexity is \(O(k)\).
Approach 2 - Vertical Iteration¶
Instead of going through row by row, we can scan column by column to find the k weakest
rows. This method takes advantage of "all the 1
's will appear to the left of all
the 0
's in each row". It doesn't need to calculate weakest condition. Instead, it
finds the first 0s for each columns. The order of 0s found is the order of k weakest rows.
class Solution:
def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]:
n_rows, n_cols = len(mat), len(mat[0])
k_weakest_rows = []
# Scan matrix column by column
for i_col in range(n_cols):
for j_row in range(n_rows):
if len(k_weakest_rows) == k:
return k_weakest_rows
# (1)
if mat[j_row][i_col] == 0 and (
i_col == 0 or mat[j_row][i_col - 1] == 1
):
k_weakest_rows.append(j_row)
# (2)
i_row = 0
while len(k_weakest_rows) < k:
# (3)
if mat[i_row][-1] == 1:
k_weakest_rows.append(i_row)
i_row += 1
return k_weakest_rows
- Check whether it is the first 0 in the corresponding row, i.e., left is 1 and
current is 0,
[..., 1, 1, 0, 0, ...]
. - Edge cases: number of rows with 0s < k, indicating some of the first k weakest rows are entirely 1s.
- If last element in the row is 1, indicating the whole row is 1s, which is not included yet.
Complexity Analysis of Approach 2¶
- Time complexity: \(O(m n)\)
- Scanning matrix column by column to find k first 0s, taking \(O(m n)\) time.
- For edge cases, it takes at most \(O(k)\) iterations to find the rest of weakest rows.
- So the total time complexity is \(O(m n)\).
- Space complexity: \(O(k)\)
The final result list store \(k\) indices.
Comparison of Different Approaches¶
The table below summarize the time complexity and space complexity of different approaches:
Approach | Time Complexity | Space Complexity |
---|---|---|
Approach 1 - Heap | \(O(m \log n + m \log k)\) | \(O(k)\) |
Approach 2 - Vertical Iteration | \(O(m n)\) | \(O(k)\) |
Test¶
- All rows have the same number of soldiers → Return the first
k
rows by index.
- Rows with only 0s → Weakest rows should be the ones with all 0s.
- Rows with all 1s → Strongest rows should be last.
- Large matrix (m,n>1000)(m,n>1000) → Binary search + heap is best.