这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战
题目
881. 救生艇
解析
既然是要多少条船,那么最朴素的方式就是:
- 一个一个往里面塞,塞满了我们从头开始,并标记我们选了哪些人
基于这个朴素的想法,我们构建如下的解答:
public static int numRescueBoats(int[] people, int limit) {
boolean[] select = new boolean[people.length];
int use = 0;
for (int i = 0; i < people.length; i++) {
if(select[i]) continue;
int total = people[i];
select[i] = true;
for (int j = i + 1; j < people.length; j++) {
if(select[j] || total + people[j] > limit) continue;
total+= people[j];
select[j] = true;
}
use++;
}
return use;
}
然后我们直接在这个case上挂了:
[21,40,16,24,30]
50
我们多输出了一个,预期3我们输出4。
因为按照我们的分组,我们4个装载如下:
[21,16] [40] [24] [30]
但其实3个就够了:
[21,24] [40] [16,30]
也就是说我们的空间利用率不够。
为了避免这个情况,我们可以:
-
将数组先排序,例如上面的,排序为:[16,21,24,30,40]
-
随后我们从大端取一个作为开始,从小端取剩余的装船
例如上面的例子:
[40] [30,->16] [24,->21]
同时注意到题目中的限定条件: 每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
我们用代码描述下我们第二次的代码:
public static int numRescueBoats2(int[] people, int limit){
boolean[] select = new boolean[people.length];
int use = 0;
Arrays.sort(people);
for (int i = people.length - 1; i >= 0; i--) {
//因为我们是从右往左取,那么如果取到读过了的数,说明已经装进去了,那么再之前的肯定也已经装进去了,我们就直接返回了
if(select[i]) break;
int total = people[i];
select[i] = true;
//i往右的数我们都装了
for (int i1 = 0; i1 < i; i1++) {
if(select[i1]) continue;
//我们已经排序过了,如果此时就大,那么后续也一样会大
if(total + people[i1] > limit) break;
select[i1] = true;
break;
}
use ++;
}
return use;
}
结果:
执行用时:2085 ms, 在所有 Java 提交中击败了5.22%的用户
内存消耗:46 MB, 在所有 Java 提交中击败了99.47%的用户
虽然是执行通过了,但是这个成绩显然很慢,我们有什么方法可以快一点吗?
-
其实我们知道:排过序之后,一旦右边的数确定了,实际上每次我们取数的时候有一个隐含的条件是:
- 我们下一次取的数,带上的数要么是剩下数最小的,要么就带不上
那么我们就把第二个循环去掉变成变量维护即可,同样地我们也不要这个布尔数组了:
public static int numRescueBoats2(int[] people, int limit){
int use = 0;
Arrays.sort(people);
int left = 0;
for (int i = people.length - 1; i >= left; i--) {
if(people[left]+people[i]<=limit) {
left++;
}
use ++;
}
return use;
}
执行用时:16 ms, 在所有 Java 提交中击败了95.52%的用户
内存消耗:47.4 MB, 在所有 Java 提交中击败了33.16%的用户
\