问题描述
输入输出
解题思路
涉及到数的大小的话,那肯定是先从小到大排序一下。
然后根据题目的样例说明,看上去像排序后两两取右边?
当然不是,蓝桥杯经典不当人样例误导。
想出以下例子,即在右边放一些特别大的值。
显而易见,因为值过大,所以毫无疑问肯定是要选两个200并免费取走80的。
接下来能免费拿的值是2,因为4和5都大于等于2的两倍。
接下来1、1、2无法买二赠一,只能全部买下。
根据上图我们可以推测出一种贪心解法:首先排序,然后从右边开始往左边遍历,若一个数找到两个大于其两倍的数就说明可以配对。
再用这种解法看一下题目样例,发现是适用的。
代码尝试一(过70%)
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int[] zu = new int[n];
for (int i = 0; i < n; i++) {
zu[i] = scan.nextInt();
}
// 最终结果
int ans = 0;
// 根据个数进行分类
if (n == 1) {
ans = zu[0];
} else if (n == 2) {
ans = zu[0] + zu[1];
} else {
// 从小到大排序
Arrays.sort(zu);
// 从倒数第三个开始判断
for (int i = n - 3; i >= 0; i--) {
int num = 0;
int[] big = new int[2]; // 存储找到的两个大值
for (int j = n - 1; j > i; j--) {
// 判断是否已被购买
if (zu[j] == -1) {
continue;
}
// 判断是否大于等于当前值的两倍
if (zu[j] >= zu[i] * 2) {
big[num++] = j;
}
// 若找到两个数则结束寻找
if (num == 2) {
break;
}
}
// 若找到两个大值则更新ans,并将这三个数赋值-1标记为已购买
if (num == 2) {
// System.out.println(zu[i] + " " + zu[big[0]] + " " + zu[big[1]]);
ans += zu[big[0]] + zu[big[1]];
zu[i] = zu[big[0]] = zu[big[1]] = -1;
}
}
// 再遍历一遍购买剩余商品
for (int i = 0; i < n; i++) {
if (zu[i] != -1) {
ans += zu[i];
}
}
}
System.out.println(ans);
}
}
过了70%数据,后面30%报超时,说明思路没有错,就是需要优化一下时间复杂度。
Arrays.sort的复杂度是O(n log n),主循环的时间复杂度是O(),可见需要优化主循环。
经过观察可知,取的两个大值都是最右边的两个,因此我们可以将内层循环改为队列,这样就将时间复杂度优化到O(n)了。
同时注意到数据的最大值为,超过了int的最大值,因此将数据类型由int改为long。
AC代码
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
long[] zu = new long[n];
for (int i = 0; i < n; i++) {
zu[i] = scan.nextInt();
}
// 最终结果
long ans = 0;
// 根据个数进行分类
if (n == 1) {
ans = zu[0];
} else if (n == 2) {
ans = zu[0] + zu[1];
} else {
// 从小到大排序
Arrays.sort(zu);
Queue<Integer> queue = new LinkedList<>();
int max1 = n - 1, max2 = n - 2; // 弹出两个最大值的下标
boolean used = false; // 记录两个最大值是否被使用,若被使用则需要重新弹出
// 从倒数第三个开始判断
for (int i = n - 3; i >= 0; i--) {
// 若弹出的两个最大值已被使用,则需要重新弹出
if (used) {
// 若队列中小于两个值,显然无法凑成3个数进行配对,因此把当前值加入队列
if (queue.size() < 2) {
queue.add(i);
continue;
} else {
max1 = queue.poll();
max2 = queue.poll();
used = false;
}
}
// 判断当前三个数是否满足配对条件
if (zu[max1] >= 2 * zu[i] && zu[max2] >= 2 * zu[i]) {
ans += zu[max1] + zu[max2];
used = true;
} else {
queue.add(i);
}
}
// 若弹出的两个数未被使用则也需要购买
if (!used) {
ans += zu[max1] + zu[max2];
}
// 队列中的剩余值也需要购买
while (!queue.isEmpty()) {
ans += zu[queue.poll()];
}
}
System.out.println(ans);
}
}
(by 归忆)