题目:
给你一个大小为 m * n 的矩阵 mat,矩阵由若干军人和平民组成,分别用 1 和 0 表示。
请你返回矩阵中战斗力最弱的 k 行的索引,按从最弱到最强排序。
如果第 i 行的军人数量少于第 j 行,或者两行军人数量相同但 i 小于 j,那么我们认为第 i 行的战斗力比第 j 行弱。
军人 总是 排在一行中的靠前位置,也就是说 1 总是出现在 0 之前。
示例 1:
输入:mat =
[[1,1,0,0,0],
[1,1,1,1,0],
[1,0,0,0,0],
[1,1,0,0,0],
[1,1,1,1,1]],
k = 3
输出:[2,0,3]
解释:
每行中的军人数目:
行 0 -> 2
行 1 -> 4
行 2 -> 1
行 3 -> 2
行 4 -> 5
从最弱到最强对这些行排序后得到 [2,0,3,1,4]
示例 2:
输入:mat =
[[1,0,0,0],
[1,1,1,1],
[1,0,0,0],
[1,0,0,0]],
k = 2
输出:[0,2]
解释:
每行中的军人数目:
行 0 -> 1
行 1 -> 4
行 2 -> 1
行 3 -> 1
从最弱到最强对这些行排序后得到 [0,2,3,1]
提示:
- m == mat.length
- n == mat[i].length
- 2 <= n, m <= 100
- 1 <= k <= m
- matrix[i][j] 不是 0 就是 1
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/the-k-weakest-rows-in-a-matrix
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路:
根据题目中的意思:军人总是排在一行中的靠前位置,1 总是出现在 0 之前,且二维矩阵中不是0就是1。就可以用二分查找,找出一行中最后一个1 的位置,设置位置的初始值pos为-1,则得知最后一个1的位置后,这一行1的个数就为pos+1。
二分查找的细节:
- 设置left = 0, right = col - 1, pos = -1, mid = left + (right - left + 1) / 2;
- 循环的条件是:left < right;
- 如果 mat[i][mid] == 0,则说明mid右边的数都为0,需要移动right到mid-1,搜索范围为[left, mid -1];
- 如果 mat[i][mid] == 1,则说明mid左边的数都为1,但是mid也有可能是最后一个1的位置,故将 left 移动到 mid,搜索范围为[mid, right](所以mid需要向上取整);
- 循环结束后:left == right,需要判断left处的值是否为1,为1则pos = left,否则pos还是循环中的pos。
得到每一行的战斗力后,可以将每一行的战斗力和其索引放入到一个小跟堆里面,但是当战斗力相同时,索引较小的更弱,故需要在小根堆中存放战斗力和索引的二元组。
java代码(left < right):
1 class Solution { 2 public int[] kWeakestRows(int[][] mat, int k) { 3 int row = mat.length, col = mat[0].length; 4 //建立一个小跟堆 5 PriorityQueue<int[]> queue = new PriorityQueue<>((x1, x2) -> { 6 //如果两行个数不相同,就按照个数升序 7 if (x1[0] != x2[0]){ 8 return x1[0] - x2[0]; 9 } else { 10 //如果两行个数相同,就按照行索引升序 11 return x1[1] - x2[1]; 12 } 13 }); 14 //二分查找,找到每行1的个数 15 for (int i = 0; i < row; i++){ 16 int pos = -1; 17 int left = 0, right = col - 1; 18 while (left < right){ 19 int mid = left + (right - left + 1) / 2; 20 if (mat[i][mid] == 0){ 21 right = mid - 1; 22 }else { 23 left = mid; 24 //更新pos的位置 25 pos = mid; 26 } 27 } 28 pos = mat[i][left] == 1 ? left : pos; 29 //pos+1:pos为位置,pos+1就为长度 30 queue.offer(new int[]{pos+1, i}); 31 } 32 int[] res = new int[k]; 33 for (int l = 0; l < k; l++){ 34 res[l] = queue.poll()[1]; 35 } 36 return res; 37 } 38 }
python3代码(left <= right):
1 class Solution: 2 def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]: 3 row, col = len(mat), len(mat[0]) 4 power = list() 5 for i in range(row): 6 left, right, pos = 0, col-1, -1 7 while left <= right: 8 mid = left + (right - left) // 2 9 if mat[i][mid] == 0: 10 right = mid - 1 11 else: 12 left = mid + 1 13 pos = mid 14 power.append((pos+1, i)) 15 # 将列表转换为堆 16 heapq.heapify(power) 17 res = list() 18 for i in range(k): 19 res.append(heapq.heappop(power)[1]) 20 return res
小知识:
1.java使用优先队列实现大顶堆和小顶堆,默认是小根堆,当然记不住默认也没有关系
小根堆创建:
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k,(a,b) -> a-b);
大跟堆创建:
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k,(a,b) -> b-a);
其中构造器中的k表示创建堆的大小,之后用Lambda表达式快速实现自定义排序。
2.heapq堆的常用方法:
- heapq.heapify(list): 将列表转换为堆
- heapq.heappush(heap, itme):heap为定义堆,item增加的元素
- heapq.heappop(heap):删除并返回最小值,因为堆的特征是heap[0]永远是最小的元素,所以一般都是删除第一个元素
- heapq.heapreplace(heap.item) :删除并返回最小元素值,添加新的元素值
- heap.heappushpop(list, itme):判断添加元素值与堆的第一个元素值对比;如果大,则删除并返回第一个元素,然后添加新元素值item;如果小,则返回item,原堆不变
- heap.nlargest(n, heap):查询堆中的最大n个元素
- heap.nsmallest(n, heap):查询堆中的最小n个元素