Java中的滑动窗口最大值

Java中的滑动窗口最大值

 

在这篇文章中,我们将看到有关Java滑动窗口最大值的信息


问题

给定一个整数数组和一个整数k,从大小为K的所有连续子数组中找到的最大元素。

例如:

输入:int [] arr = {2,6,-1,2,4,1,-6,5}
int k = 3
输出:6,6,4,4,4,5

对于每个大小为k的子数组,打印其最大元素。


解决方案

天真的方法:
基本的解决方案是只生成所有大小为k的连续子数组,然后遍历它们,以找出当前子数组中的最大值。
考虑到,对于每个点,我们基本上都采用下一个“ k”元素,然后循环遍历这k个元素,因此该算法的最差时间复杂度为O(n * k)

 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 
package Arrays;
 
import java.util.Scanner;
 
public class slidingWindowMax {
 
public static void main(String[] args) {
 
Scanner scn = new Scanner(System.in);
int [] arr = new int[scn.nextInt()];
 
for(int i = 0; i < arr.length; i++)
{
arr[i] = scn.nextInt();
}
 
int windowSize = scn.nextInt();
 
solve(arr, windowSize);
 
}
 
public static void solve(int[] arr, int k)
{
// starting the outer loop from k and running it until,
                // current pointer is EQUAL to arr.length
for(int i = k; i <= arr.length; i++)
{
int max = Integer.MIN_VALUE;
 
// this loop considers subarrays of size k ending at i-1
for(int j = i-k; j<i; j++)
{
max = Math.max(max, arr[j]);
}
 
System.out.println(max);
}
}
}
 

略有效率的方法:

通过使用细分树,我们可以肯定地减少为每个子数组找到最大值所需的时间
我们可以为给定的数组实现一个分段树,并且可以通过范围查询[i,i + k-1]获得每个子数组的最大值。

  • 段树中节点的总数
    构造段树的最差时间复杂度为O(n),因为我们知道:
    (i)段树的叶节点包含数组的所有元素。
    (ii)最后一级上的节点数是所有上一级上的节点数。
  • 在数学上
  1. 考虑数组的长度为n,因此,段树的叶节点将为n。
  2. 因此,所有较高级别上的节点数将为n-1。
  3. 长度为n的数组的分段树上的总节点为:
    Tn =叶节点+较高级别的节点
          = n + n-1
          = 2n + 1
  • 复杂度分析

我们的分段树的构造只涉及每个节点一次的计算,因此分段树构造的最差时间复杂度将是O(2n + 1),即O(n)。
每个子数组范围查询的结果将在O(logk)中计算。
将对所有大小为k的'n-k + 1'个子数组进行查询计算。
因此,此算法的总体时间复杂度将为O((n-k + 1)* logk),即O(nlogk)

 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
 
package Arrays;
 
import java.util.Scanner;
 
public class slidingWindowMax {
 
static int[] sarr;
 
public static void main(String[] args) {
 
Scanner scn = new Scanner(System.in);
int[] arr = new int[scn.nextInt()];
 
for (int i = 0; i < arr.length; i++) {
arr[i] = scn.nextInt();
}
 
int windowSize = scn.nextInt();
 
int height = (int)Math.ceil((Math.log(arr.length) / Math.log(2)));
 
/* size of segment array  i.e. the number of nodes will be = [(2^height+1)-1] */
 
sarr = new int[1<<height -1];
 
construct(0, 0, arr.length-1, arr);
 
solve(arr, windowSize);
 
}
 
public static void solve(int[] arr, int k) {
for (int i = 0; i <= arr.length - k; i++) {
/* finding the result for range query from i to i+k which is basically a subarray.
*
*/
System.out.println(query(0, i, i + k - 1, 0, arr.length - 1));
}
}
 
public static int construct(int idx, int start, int end, int[] arr) {
/* leaf nodes contains the array elements */
if (start == end) {
sarr[idx] = arr[end];
return sarr[idx];
}
 
int mid = (start + end) / 2;
/* dividing the range for every node in segment tree into two halves */
int left = construct(2 * idx + 1, start, mid, arr);
int right = construct(2 * idx + 2, mid + 1, end, arr);
/* result for current index in segment tree will be calculated
*  in post order, and will be maximum of its two childs.
*/
sarr[idx] = Math.max(left, right);
return sarr[idx];
}
 
public static int query(int idx, int queryStart, int QueryEnd, int start, int end) {
/* if our range is completely outside the query,
* we need to return a result such that it causes no effect in our final answer.
*/
 
if (start > QueryEnd || end < queryStart) {
return Integer.MIN_VALUE;
}
/* if the range of the current segment falls completely
*  inside the query then return its value.
*/
else if (start >= queryStart && end <= QueryEnd) {
return sarr[idx];
} else {
 
int mid = (start + end) / 2;
int left = query(2 * idx + 1, queryStart, QueryEnd, start, mid);
int right = query(2 * idx + 2, queryStart, QueryEnd, mid + 1, end);
 
return Math.max(left, right);
}
}
}
 

最有效的方法

在这种方法中,我们使用Deque来帮助我们找到O(n)中的滑动窗口最大值。

  • 的Deque基本上是一个队列,其是在两个两个排队和deque,即,可以添加或删除元素或者从前面或后面的端部开放。

我们实际上要解决的问题是:

我们将子数组的k个元素按相反的顺序保留,我们不需要保留所有k个元素,尽管稍后我们将在代码中看到。

  • 生成前k个元素的双端队列,使它们以相反的顺序排序,以使最大元素位于最前面。
  • 如果Deque为空,则直接添加元素,否则检查传入的元素是否大于最后一个元素,如果是,则从最后弹出元素,直到剩余的Deque的最后一个元素大于传入的元素。
  • 我们还需要删除属于不同子数组的元素。也就是说,双端队列中的索引必须在[i,i + k]范围内。

仅在两种情况下会删除元素:
(i)如果即将到来的元素大于后面 的元素,则它将继续弹出该元素,直到剩余出队的后面有更大的元素为止,因为我们需要保持数组以相反的顺序排序。
(ii)如果元素属于任何其他子数组,则没有必要保留它。

 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
 
package org.arpit.java2blog;
 
import java.util.LinkedList;
import java.util.Scanner;
 
public class SlidingWindowMax {
 
static int[] sarr;
 
public static void main(String[] args) {
 
Scanner scn = new Scanner(System.in);
int[] arr = new int[scn.nextInt()];
 
for (int i = 0; i < arr.length; i++) {
arr[i] = scn.nextInt();
}
 
System.out.print("arr[]: {");
for (int i = 0; i < arr.length; i++) {
System.out.print(" "+arr[i]);
}
 
System.out.println(" }");
 
int windowSize = scn.nextInt();
 
solveEfficient(arr, windowSize);
 
}
 
public static void solveEfficient(int[] arr, int k) {
LinkedList<Integer> deque = new LinkedList<>();
 
for (int i = 0; i < arr.length; i++) {
 
/* keep removing the elements from deque
* which are smaller than the current element,
* because we need to keep our deque sorted in dec order
*/
while (!deque.isEmpty() && arr[deque.getLast()] <= arr[i]) {
deque.removeLast();
}
 
/* removing the i-k element, because that element does not belong
* to the subarray we are currently working on.
*/
while (!deque.isEmpty() && deque.getFirst() <= i - k) {
deque.removeFirst();
}
 
deque.addLast(i);
 
if(i >= k-1)
{  
/* only print when we have processed atleast k elements
* to make the very first subarray
*/
System.out.print(" "+arr[deque.getFirst()]);
}
 
}
}
}
 

当您在程序上方运行时,将获得以下输出:

8
2 6 -1 2 4 1 -6 5
arr []:{2 6 -1 2 4 1 -6 5}
3
6 6 4 4 4 5

这就是Java中的最大滑动窗口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值