import java.util.Comparator; import java.util.PriorityQueue; import java.util.Scanner; public class Main { public static void main(String[] args) { // 创建Scanner对象用于读取输入 Scanner sc = new Scanner(System.in); // 读取两个整数n和m,分别表示数组的长度和窗口的大小 int n = sc.nextInt(); int m = sc.nextInt(); // 初始化两个数组a和b,用于存储输入的两个数组 long a[] = new long[n + 1]; long b[] = new long[n + 1]; // 读取数组a的元素 for (int i = 1; i <= n; i++) a[i] = sc.nextLong(); // 读取数组b的元素 for (int i = 1; i <= n; i++) b[i] = sc.nextLong(); // 初始化前缀和数组pre和后缀和数组hen long pre[] = new long[n + 1]; long hen[] = new long[n + 1]; // 创建两个优先队列,用于维护窗口内的最大值 PriorityQueue<Long> q1 = new PriorityQueue<>(Comparator.reverseOrder()); PriorityQueue<Long> q2 = new PriorityQueue<>(Comparator.reverseOrder()); // 计算数组a的前缀和 for (int i = 1; i <= m; i++) { pre[i] = pre[i - 1] + a[i]; } // 计算数组b的后缀和 hen[n] = b[n]; for (int i = n - 1; i >= n - m + 1; i--) { hen[i] = hen[i + 1] + b[i]; } // 维护数组a的窗口最大值 for (int i = 1; i <= n; i++) { q1.add(a[i]); if (q1.size() > m) { long t1 = q1.poll(); pre[i] = pre[i - 1]; pre[i] += (a[i] - t1); } } // 维护数组b的窗口最大值 for (int i = n; i >= 1; i--) { q2.add(b[i]); if (q2.size() > m) { long t2 = q2.poll(); hen[i] = hen[i + 1]; hen[i] += (b[i] - t2); } } // 枚举分界点,计算最小值 long ans = Long.MAX_VALUE; for (int i = m; i <= n - m; i++) { ans = Math.min(ans, pre[i] + hen[i + 1]); } // 输出结果 System.out.println(ans); } }
-
基本语法:
-
变量声明和初始化。
-
循环结构(
for
循环)。 -
条件判断(
if
语句)。
-
-
数据结构:
-
数组(
long[]
)的使用,用于存储输入的数组和前缀和、后缀和。 -
PriorityQueue
优先队列的使用,用于维护一个动态的窗口最大值。
在Java中,PriorityQueue 是一个基于优先级堆的无界优先队列,它使用自然排序或者通过在构造器中指定的 Comparator 来排序其元素。以下是关于 PriorityQueue 在这段代码中如何用于维护一个动态窗口最大值的详细解释: PriorityQueue 的基本特性: 优先级排序:PriorityQueue 会根据元素的优先级顺序来排列元素。默认情况下,它是一个最小堆,但你可以通过传递一个 Comparator 实例来使其成为一个最大堆。 动态调整:当元素被添加到队列中或者从队列中移除时,PriorityQueue 会自动调整元素的位置以保持堆的特性。 在代码中的应用: 在提供的代码片段中,PriorityQueue 被用来维护滑动窗口中的最大值。这里是具体的应用步骤: 初始化两个优先队列: PriorityQueue<Long> q1 = new PriorityQueue<>(Comparator.reverseOrder()); PriorityQueue<Long> q2 = new PriorityQueue<>(Comparator.reverseOrder()); 这里创建了两个最大堆,q1 和 q2,分别用于处理数组 a 和 b 的元素。 填充优先队列: 在处理数组时,代码会将窗口内的元素添加到对应的优先队列中。例如,对于数组 a,代码可能会这样做: for (int i = 0; i < m; i++) { q1.add(a[i]); } 这样,q1 中就包含了数组 a 的前 m 个元素,且队列的头部始终是这 m 个元素中的最大值。 维护窗口: 当窗口在数组上滑动时,需要从队列中移除窗口前面的元素,并添加新的元素。例如: if (q1.size() > m) { q1.poll(); // 移除窗口前面的元素 } q1.add(a[i]); // 添加新的元素到窗口 通过这种方式,q1 始终保持窗口内的最大值在队列头部。 获取窗口最大值: 由于 q1 是一个最大堆,所以队列头部的元素就是窗口内的最大值。可以使用 q1.peek() 来获取这个值而不移除它,或者使用 q1.poll() 来获取并移除这个值。 为什么使用 PriorityQueue? 使用 PriorityQueue 而不是其他数据结构(如数组或列表)的原因是它可以更高效地维护窗口的最大值。在数组或列表中,每次窗口滑动时,都需要遍历窗口内的所有元素来找到最大值,这会导致时间复杂度为 O(m)。而使用 PriorityQueue,添加和移除元素的操作的时间复杂度是 O(log m),因此总体上可以更高效地处理滑动窗口问题。 总之,PriorityQueue 在这段代码中用于动态地跟踪和维护滑动窗口中的最大值,这是解决某些特定类型数组问题的有效方法。
-
-
算法:
-
前缀和算法,用于计算数组前
i
个元素的和。 -
后缀和算法,用于计算数组从第
i
个元素到末尾的和。 -
滑动窗口算法,通过优先队列来维护窗口内的最大值。
-
-
Java标准库:
-
Scanner
类的使用,用于读取输入。 -
Comparator
接口的使用,用于定义优先队列的排序规则。在Java中,Comparator接口是一个功能强大的工具,它允许程序员定义自己的排序规则,而不必依赖于对象类的自然排序(即实现Comparable接口的排序)。Comparator接口在许多场景中都非常有用,尤其是在使用集合框架中的排序和搜索操作时,比如在PriorityQueue、TreeSet或Collections.sort()方法中。 以下是关于Comparator接口的进一步解释: Comparator接口的基本概念: Comparator是一个函数式接口,这意味着它只有一个抽象方法,即compare(T o1, T o2),它用于比较两个类型为T的对象。 compare方法接受两个参数,并根据以下规则返回一个整数值: 如果o1小于o2,则返回负整数。 如果o1等于o2,则返回零。 如果o1大于o2,则返回正整数。 Comparator接口的使用: 在PriorityQueue的上下文中,Comparator用于定义队列中元素的排序规则。以下是如何使用Comparator来创建一个最大堆的例子: PriorityQueue<Long> q1 = new PriorityQueue<>(Comparator.reverseOrder()); 在这个例子中,Comparator.reverseOrder()是一个静态方法,它返回一个Comparator实例,该实例对实现了Comparable接口的对象的自然顺序进行反转。对于基本类型Long,这意味着队列将按照数值的降序排列,即队列头部将始终是最大的元素。 如果你想要定义一个自定义的排序规则,你可以创建一个实现了Comparator接口的匿名类或者使用lambda表达式,如下所示: // 使用匿名类 PriorityQueue<Long> q1 = new PriorityQueue<>(new Comparator<Long>() { @Override public int compare(Long o1, Long o2) { return o2.compareTo(o1); // 反转比较,实现最大堆 } }); // 使用lambda表达式 PriorityQueue<Long> q1 = new PriorityQueue<>((o1, o2) -> o2.compareTo(o1)); 在上述代码中,无论是使用匿名类还是lambda表达式,我们都定义了一个比较器,它将两个Long对象按照降序排列。 为什么使用Comparator? 使用Comparator的主要优势在于它的灵活性和可定制性。它允许你: 为没有实现Comparable接口的类定义排序规则。 为已经实现Comparable接口的类提供不同的排序规则。 在运行时动态地改变排序规则,而不必修改类的内部实现。 总之,Comparator接口是Java集合框架中一个强大的工具,它使得排序和搜索操作更加灵活和可定制。
-
-
数学运算:
-
使用
Math.min
函数来找出最小值。
-