
在实际开发中,我们经常需要处理两个数组之间的元素关系。一个常见的需求是:给定两个整数数组 a 和 b,对于 b 中的每一个元素 b_i,我们需要统计数组 a 中有多少个元素 a_j 满足 a_j >= b_i。最终,将这些统计结果按顺序收集到一个列表中。
例如,如果 a = [1, 2, 3, 4, 5] 和 b = [6, 5, 4, 3, 2],期望的输出是 [0, 1, 2, 3, 4]。
最初的实现通常会采用嵌套循环的方式,遍历 b 中的每个元素,然后在内部循环遍历 a 中的所有元素进行比较。
import java.util.ArrayList;
import java.util.List;
public class ArrayComparison {
/**
* 传统方法:使用嵌套循环比较两个数组元素。
* 时间复杂度为 O(a.length * b.length)。
*/
public static List<Integer> giantArmyTraditional(int[] a, int[] b) {
List<Integer> resultList = new ArrayList<>();
// 处理特殊情况,如果a数组只有一个0元素,则直接返回[0]
// (此条件可能源于特定业务需求,通常可省略)
if (a.length == 1 && a[0] == 0) {
resultList.add(0);
return resultList;
}
for (int i = 0; i < b.length; i++) {
int count = 0; // 每次循环b数组元素时重置计数器
for (int j = 0; j < a.length; j++) {
if (a[j] >= b[i]) {
count++;
}
}
resultList.add(count);
}
return resultList;
}
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
int[] b = {6, 5, 4, 3, 2};
System.out.println("传统方法结果: " + giantArmyTraditional(a, b)); // 输出: [0, 1, 2, 3, 4]
}
}上述传统方法的时间复杂度是 O(M * N),其中 M 是数组 b 的长度,N 是数组 a 的长度。当 M 和 N 都非常大时(例如,达到数十万甚至数百万),这种方法会导致执行时间呈平方级增长,性能会变得非常低下,甚至可能导致程序超时。因此,对于大规模数据集,我们需要一种更高效的算法。
为了显著提升性能,我们可以利用排序和二分查找的组合。核心思想是:如果数组 a 是有序的,那么查找“大于等于某个值”的元素数量就会变得非常高效。
立即学习“Java免费学习笔记(深入)”;
Java 的 Arrays.binarySearch(int[] a, int key) 方法有以下行为:
我们可以利用“插入点”来计算大于等于 key 的元素数量。如果 key 未找到,返回值为负数 index,那么实际的插入点就是 (-index - 1)。这个插入点之前的元素都小于 key,而从插入点开始到数组末尾的所有元素都大于或等于 key。因此,大于等于 key 的元素数量就是 a.length - 插入点。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArrayComparisonOptimized {
/**
* 优化方法:利用排序和二分查找高效比较两个数组元素。
* 整体时间复杂度为 O(N log N + M log N),其中N为a的长度,M为b的长度。
* 当N和M相近时,可简化为 O(N log N)。
*/
public static List<Integer> giantArmyOptimized(int[] a, int[] b) {
int aLength = a.length;
List<Integer> resultList = new ArrayList<>();
// 步骤1: 对数组 a 进行升序排序
// 这一步的时间复杂度为 O(N log N)
Arrays.sort(a);
// 步骤2: 遍历数组 b 中的每个元素
// 对每个元素执行二分查找,每次查找的时间复杂度为 O(log N)
// 总计 M 次查找,因此这部分的时间复杂度为 O(M log N)
for (int bElement : b) {
// 步骤3: 在已排序的 a 数组中查找 bElement
int index = Arrays.binarySearch(a, bElement);
// 如果 bElement 未在 a 中找到,Arrays.binarySearch 返回一个负值
// 这个负值是 -(插入点) - 1
// 因此,实际的插入点是 -index - 1
if (index < 0) {
index = -index - 1;
}
// 此时的 index 表示:
// 如果 bElement 在 a 中,index 是其首次出现的位置。
// 如果 bElement 不在 a 中,index 是它应该被插入的位置,
// 使得其左侧元素都小于它,右侧元素都大于等于它。
// 那么,数组 a 中大于等于 bElement 的元素数量就是 a.length - index
resultList.add(aLength - index);
}
return resultList;
}
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5};
int[] b = {6, 5, 4, 3, 2};
System.out.println("优化方法结果: " + giantArmyOptimized(a, b)); // 输出: [0, 1, 2, 3, 4]
int[] a2 = {10, 20, 30, 40, 50};
int[] b2 = {15, 35, 60, 5, 30};
// 预期结果:
// 15: a中有 [20, 30, 40, 50] 共 4 个 >= 15
// 35: a中有 [40, 50] 共 2 个 >= 35
// 60: a中有 [] 共 0 个 >= 60
// 5: a中有 [10, 20, 30, 40, 50] 共 5 个 >= 5
// 30: a中有 [30, 40, 50] 共 3 个 >= 30
// 结果应为 [4, 2, 0, 5, 3]
System.out.println("优化方法结果 (示例2): " + giantArmyOptimized(a2, b2));
}
}时间复杂度:
空间复杂度:
示例演示: a = [1, 2, 3, 4, 5] (排序后仍是 [1, 2, 3, 4, 5]),b = [6, 5, 4, 3, 2]
最终结果为 [0, 1, 2, 3, 4],与预期一致。
通过对数组 a 进行一次性排序,并结合对数组 b 中每个元素的二分查找,我们成功地将问题的时间复杂度从 O(N*M) 降低到 O(N log N + M log N)。这种优化对于处理大数据集至关重要,能够显著提高程序的执行效率和响应速度。在面对类似的数组比较和计数问题时,应优先考虑这种利用排序和二分查找的策略。
以上就是Java数组高效比较:利用排序与二分查找实现O(N log N)性能优化的详细内容,更多请关注php中文网其它相关文章!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号