巧克力板选择问题
问题描述
小M想携带尽可能多的巧克力板参加春游。她有 n 块巧克力板,每块巧克力的边长为 ai,重量等于边长的平方。小M有多个背包,每个背包都有最大承重限制。我们需要帮助她计算在每个背包的承重范围内,最多可以携带多少块巧克力板。
示例输入输出
- 输入: n = 5,边长数组 a = [1, 2, 2, 4, 5],背包承重限制 queries = [1, 3, 7, 9, 15]。
- 输出: [1, 1, 2, 3, 3]
每个背包的承重限制对应的最大可携带巧克力块数量分别为 1、1、2、3、3。
问题分析
解题思路
- 重量计算:将每块巧克力的边长平方,得到重量。
- 排序与前缀和:将巧克力按重量从小到大排序,并计算其前缀和,方便快速求解任意承重下的最大块数。
- 二分查找优化:对于每个背包的最大承重,使用二分查找快速定位最多可携带的巧克力块数。
详细步骤解析
1. 巧克力重量计算
每块巧克力的重量为其边长的平方:
- 如果边长为 1,则重量为 1^2 = 1。
- 如果边长为 2,则重量为 2^2 = 4。
我们将所有重量存入数组,方便后续处理。
2. 排序与前缀和
对重量数组排序后,通过前缀和快速计算任意范围内的总重量。前缀和数组的定义为: 这样我们可以快速获取任意前缀的总重量。
3. 二分查找优化
使用二分查找在前缀和数组中定位不超过背包承重的最大重量位置,从而确定最多可携带的巧克力数量。二分查找将查询的时间复杂度从 O(n) 降低到 O(log n)。
代码实现
import java.util.Arrays;
public class Main {
public static int[] solution(int n, int m, int[] a, int[] queries) {
// 计算每块巧克力的重量
int[] weights = new int[n];
for (int i = 0; i < n; i++) {
weights[i] = a[i] * a[i];
}
// 排序重量数组
Arrays.sort(weights);
// 计算前缀和
int[] prefixSum = new int[n + 1];
for (int i = 0; i < n; i++) {
prefixSum[i + 1] = prefixSum[i] + weights[i];
}
// 处理每个查询,使用二分查找
int[] result = new int[m];
for (int i = 0; i < m; i++) {
int maxWeight = queries[i];
result[i] = binarySearch(prefixSum, maxWeight);
}
return result;
}
private static int binarySearch(int[] prefixSum, int maxWeight) {
int left = 0, right = prefixSum.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (prefixSum[mid] <= maxWeight) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right;
}
public static void main(String[] args) {
System.out.println(Arrays.equals(solution(5, 5, new int[]{1, 2, 2, 4, 5}, new int[]{1, 3, 7, 9, 15}), new int[]{1, 1, 2, 3, 3}));
System.out.println(Arrays.equals(solution(4, 3, new int[]{3, 1, 2, 5}, new int[]{5, 10, 20}), new int[]{2, 2, 3}));
System.out.println(Arrays.equals(solution(6, 4, new int[]{1, 3, 2, 2, 4, 6}, new int[]{8, 12, 18, 25}), new int[]{2, 3, 4, 4}));
}
}
代码讲解
- 计算重量:
对每块巧克力计算其重量并存储在数组
weights中。 - 排序与前缀和:
对重量排序,并通过循环计算前缀和,构建
prefixSum数组。 - 二分查找: 对每个查询使用二分查找,快速找到满足条件的巧克力数量。
思考与优化
- 时间复杂度: 排序的时间复杂度为 O(nlog n),每次查询使用二分查找的时间复杂度为 O(log n)。因此,总体复杂度为 O(nlog n+mlog n),适合处理大量数据。
- 优化方向: 如果背包数量远大于巧克力数量,可以考虑使用多线程或并行处理查询,进一步提升性能。
总结
这道题涉及到经典的前缀和与二分查找结合的应用。通过这次练习,我更加深刻地理解了如何在排序数组中高效查询。这种思路在解决背包问题、动态规划等领域都有广泛应用。同时,也让我意识到代码结构清晰、思路清楚的重要性。