一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情。
题目描述
给你一个大小为 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]
思路
根据题意可知,每行中军人的数量就是战斗力,求出每行的战斗力后,然后根据战斗力和行号进行从小到大排序,取前面的k行即可。所以,核心是快速求出每行1的个数,由于1总是出现在0前面,可以转换成为求最后一个1的下标index,1的个数就是index+1。
我们采用二分法,如果中间点是1,那么中间点的下标可以作为最后一个1位置的备选,更新ans,然后令left = mid + 1再向右试探;如果中间点是0,那么最后一个1肯定还要在更加左边的位置,令right = mid - 1再向左试探。
另外,还需要注意边界情况,即不含有1的情况,所以我们初始化的时候,令ans = -1。
Java版本代码
class Solution {
public int[] kWeakestRows(int[][] mat, int k) {
int m = mat.length;
Line[] lineArr = new Line[m];
for (int i = 0; i < m; i++) {
lineArr[i] = new Line(count1(mat[i]), i);
}
Arrays.sort(lineArr);
int[] ans = new int[k];
for (int i = 0; i < k; i++) {
ans[i] = lineArr[i].getIndex();
}
return ans;
}
private int count1(int[] arr) {
int ans = -1;
int left = 0, right = arr.length-1;
while (left <= right) {
int mid = left + ((right-left)>>1);
if (arr[mid] == 1) {
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans + 1;
}
class Line implements Comparable<Line> {
private int val;
private int index;
public int getVal() {
return val;
}
public int getIndex() {
return index;
}
public Line(int val, int index) {
this.val = val;
this.index = index;
}
@Override
public int compareTo(Line o) {
if (val == o.val) {
return index - o.index;
}
return val - o.val;
}
}
}