一尘不染

在数组中找到局部最小值

java

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

例如:

Input : int[] arr = {2,6,-1,2,4,1,-6,5}
int k = 3
output : 6,6,4,4,4,5

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


阅读 333

收藏
2021-06-16

共2个答案

一尘不染

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

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);
        }
    }
}

稍微有效的方法:

通过使用Segment 树,我们肯定可以减少为每个子数组寻找最大值所花费的时间。
我们可以为给定的数组实现一个段树,我们可以通过范围查询[i, i+k-1]获取每个子数组的最大值。

段树中的节点总数:
构建段树的最坏时间复杂度为O(n),因为我们知道,
(i) 段树的叶节点包含数组的所有元素。
(ii) 最后一层的节点数是所有上一层的节点数。
在数学上,
考虑数组的长度为 n,因此,线段树的叶节点将为 n。
因此,所有上层的节点数将为 n-1。
长度为 n 的数组的段树上的总节点数为:

Tn = leaf nodes + nodes on upper levels
      = n + n-1
      = 2n+1

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

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 为空,则直接添加元素,否则检查传入元素是否大于最后一个元素,如果是,则继续从最后一个元素弹出元素,直到剩余 Deque 的最后一个元素大于传入元素。
我们还需要删除属于不同子数组的元素。即 Deque 中的索引必须在[i, i+k]范围内。
一个元素只会在两种情况下被删除:
(i)如果即将到来的元素大于后部 元素,如果它,它将继续弹出元素,直到剩余出队的后部出现更大的元素,因为我们需要保持数组以相反的顺序排序。
(ii) 如果元素属于任何其他子数组,则保留它没有意义。

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
2021-06-16
一尘不染

.

2021-06-16