子数组中最大值最小值不大于给定值问题

题目描述:

给定数组arr和整数num,返回有多个子数组满足如下情况:
 max(arr[i…j]) - min(arr[i…j]) <= num
 max(arr[i…j])表子数组arr[i…j]中的最大值,min(arr[i…j])表子数组arr[i…j] 中的最小值

题目难度:


medium

题目思路:

思路一:
本题可以采用暴力遍历去求解。即遍历每个子数组,然后分别去求每个子数组的最大值与最小值之差是否满足不大于给定值,并统计满足条件的个数。

代码实现:

public static int getSubArray(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int cnt = 0;
        for (int start = 0; start < arr.length; start++) {
            for (int end = start; end < arr.length; end++) {
                if (isValid(arr, start, end, num)) {                        //对每一个子数组进行判断
                    cnt++;
                }
            }
        }
        return cnt;
    }

    private static boolean isValid(int[] arr, int start, int end, int num) {          //判断当前子数组
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int i = start; i <= end; i++) {
            max = Math.max(max, arr[i]);
            min = Math.min(min, arr[i]);
        }
        return max - min <= num;
    }

    // for test
    public static int[] getRandomArray(int len) {
        if (len < 0) {
            return null;
        }
        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            arr[i] = (int) (Math.random() * 10);
        }
        return arr;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr != null) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[] arr = getRandomArray(30);
        int num = 5;
        printArray(arr);
        System.out.println(getSubArray(arr, num));

    }

    public static int getSubArray(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int cnt = 0;
        for (int start = 0; start < arr.length; start++) {
            for (int end = start; end < arr.length; end++) {
                if (isValid(arr, start, end, num)) {                        //对每一个子数组进行判断
                    cnt++;
                }
            }
        }
        return cnt;
    }

    private static boolean isValid(int[] arr, int start, int end, int num) {          //判断当前子数组
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int i = start; i <= end; i++) {
            max = Math.max(max, arr[i]);
            min = Math.min(min, arr[i]);
        }
        return max - min <= num;
    }

    // for test
    public static int[] getRandomArray(int len) {
        if (len < 0) {
            return null;
        }
        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            arr[i] = (int) (Math.random() * 10);
        }
        return arr;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr != null) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[] arr = getRandomArray(30);
        int num = 5;
        printArray(arr);
        System.out.println(getSubArray(arr, num));

    }

思路二:
由于思路一的时间复杂度过高。
本题采用生成窗口的数据结构去求解。
1、在需要遍历的数组中设置两个指针(L、R)分别表示要遍历的子数组。刚开始L、R都在0位置,然后让L固定,R右移,然后在分别移动L、R,保证L< R即可。
2、窗口中index值的设置:分别生成最大窗口和最小窗口来记录遍历L-R子数组中的元素,其中最大窗口的队列头部保存的是子数组的最大值,最小窗口的头部保存的是子数组最小值。对于最大窗口,如果入队列的值比前面的值小,则直接添加到队列尾部,否则,要弹出队列的前一个元素,直到前一个位置大于当前元素。(窗口中记录并保存的是数组的index,如果保持数组中的值,若出现重复元素,则无法确定到底是哪个位置上的值)

代码实现:

public static int getSubArray1(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        LinkedList<Integer> qmax = new LinkedList<Integer>(); //最大窗口
        LinkedList<Integer> qmin = new LinkedList<Integer>(); //最小窗口

        int L = 0;   //左指针
        int R = 0;   //右指针
        int cnt = 0;

        while (L < arr.length) {
            while (R < arr.length) {
                while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[R]) {
                    qmin.pollLast();           //保证最小窗口值依次递增
                }
                qmin.addLast(R);

                while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {
                    qmax.pollLast();           //保证最大窗口值依次递减
                }
                qmax.addLast(R);

                if (arr[qmax.getFirst()] - arr[qmin.getFirst()] > num) {
                    break;   //一旦最大值与最小值之差大于给定值,右指针就没必要往后移动了,
                             // 因为从L开始到R的,后面包含此子数组的数组一定不满足条件
                }
                R++;
            }
            if (qmin.peekFirst() == L) {
                qmin.pollFirst();
            }
            if (qmax.peekFirst() == L) {
                qmax.pollFirst();   //如果左指针右移的时候,队列头部恰好为当前位置,则需弹出当前位置。
            }

            cnt += R - L;
            L++;

        }
        return cnt;
    }

    public static int getSubArray1(int[] arr, int num) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        LinkedList<Integer> qmax = new LinkedList<Integer>(); //最大窗口
        LinkedList<Integer> qmin = new LinkedList<Integer>(); //最小窗口

        int L = 0;   //左指针
        int R = 0;   //右指针
        int cnt = 0;

        while (L < arr.length) {
            while (R < arr.length) {
                while (!qmin.isEmpty() && arr[qmin.peekLast()] >= arr[R]) {
                    qmin.pollLast();           //保证最小窗口值依次递增
                }
                qmin.addLast(R);

                while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) {
                    qmax.pollLast();           //保证最大窗口值依次递减
                }
                qmax.addLast(R);

                if (arr[qmax.getFirst()] - arr[qmin.getFirst()] > num) {
                    break;   //一旦最大值与最小值之差大于给定值,右指针就没必要往后移动了,
                             // 因为从L开始到R的,后面包含此子数组的数组一定不满足条件
                }
                R++;
            }
            if (qmin.peekFirst() == L) {
                qmin.pollFirst();
            }
            if (qmax.peekFirst() == L) {
                qmax.pollFirst();   //如果左指针右移的时候,队列头部恰好为当前位置,则需弹出当前位置。
            }

            cnt += R - L;
            L++;

        }
        return cnt;
    }