【刷题 issue10】程序员代码面试指南 —— IT 名企算法与数据结构题目最优解

第一章 栈和队列

1.10 最大值减去最小值小于或等于 num 的子数组数量

【题目】

给定数组 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] 中的最小值。

【要求】

如果数组长度为 N,请实现时间复杂度为 O(N) 的解法。

【难度】

校 ★★★☆

【题解】

最普通的解法:找到 arr 的所有子数组,一共 O(N) 个,然后对每一个子数组做遍历找到其中的最小值和最大值,这个过程的时间复杂度为 O(N),然后判读这个子数组是否符合条件,统计所有满足的子数组数量即可。整体的时间复杂度为 O(N),显然不符合题意。

最优解可以实现时间复杂度为 O(N),额外空间复杂度 O(N),这需要借助双端队列和滑动窗口结构完成。首先,生成两个双端队列 qMax 和 qMin。当子数组为 arr[i…j] 时,qMax 维护了子窗口数组 arr[i…j] 的最大值更新的结构,qMin 维护了窗口子数组 arr[i…j] 的最小值更新的结构。当子数组 arr[i…j] 向右扩充一个位置变成 arr[i…j+1] 时,qMax 和 aMin 结构可以在 O(1) 的时间内更新,并且可以在 O(1) 的时间内得到 arr[i…j+1] 的最大值和最小值。当子数组 arr[i…j] 向左缩小一个位置变为 arr[i+1…j],qMax 和 aMin 结构依然可以在 O(1) 的时间内更新,并且在 O(1) 的时间内得到 arr[i+1…j] 的最大值和最小值。

通过上述分析,可以得到以下两个结论:

  • 如果子数组 arr[i…j] 满足条件,即 max(arr[i…j)-min(arr[i…j) ≤ num,那么 arr[i…j] 中的每一个子数组,即 arrk…m 都满足条件。以子数组 arr[i…j-1] 为例说明,arr[i…j-1] 最大值只可能小于或等于 arr[i…j] 的最大值,arr[i…j-1] 的最小值只可能大于或等于 arr[i…j] 的最小值,所以 arr[i…j-1] 必然满足条件。同理,arr[i…j] 中的每个子数组都满足条件。
  • 如果子数组 arr[i…j] 不满足条件,那么所有包含 arr[i…j] 的子数组,即 arrk…m 都不满足条件。证明同第一个结论。

根据双端队列 qMax 和 qMin 的结构性质,以及如上两个结论,设计如下:

  1. 生成两个双端队列 qMax 和 qMin。生成两个整形变量 i 和 j,表示子子数组的范围,即 arr[i…j]。生成整形变量 res,表示所有满足条件的子数组数量。
  2. 令 j 不断向右移动(j++),表示 arr[i…j] 一直向右扩大,并不断更新 qMax 和 qMin结构,保证 qMax 和 qMin 始终维持动态窗口最大值和最小值的更新结构。一旦出现 arr[i…j] 不满足条件的情况,j 向右扩的过程停止,此时 arr[i…j-1]、arr[i…j-2]、arr[i…j-3]、…、arr[i…i] 一定是满足条件的。也就是说,所有必须以 arr[i] 作为第一个元素的子数组,满足条件的数量为 j-i 个,于是 res+=(j-i)。
  3. 当进行完步骤 2,令 i 向右移动一个位置,并对 qMax 和 qMin 做相应的更新,qMax 和 qMin 从原来的 arr[i…j] 窗口变为 arr[i+1…j] 窗口的最大值和最小值的更新结构。然后重复步骤 2,求出以 arr[i+1] 作为第一个元素的子数组中满足条件的数量。
  4. 根据步骤 2 和步骤 3,依次求出以 arr[0]、arr[1]、…、arr[N-1] 作为第一个元素的子数组中满足条件的数量,累加起来就是最终的结果。

上述过程中,所有的下标值最多进出 qMax 或 qMin 一次,所以整个过程的时间复杂度为 O(N)。

【实现】

  • MaxMinusMinLENum.java
import java.util.Deque;
import java.util.LinkedList;

/**
 * 最大值减最小值小于或等于 num 的子数组数量
 */
public class MaxMinusMinLENum {

    private int[] arr;
    private int num;
    private int count;

    public MaxMinusMinLENum(int[] arr, int num) {
        this.arr = arr;
        this.num = num;
        this.count = -1;
    }

    public void setArr(int[] arr) {
        this.arr = arr;
        process();
    }

    public void setNum(int num) {
        this.num = num;
        process();
    }

    public int getCount() {
        if (count < 0) {
            process();
        }
        return this.count;
    }

    private void process() {
        /**
         * 特殊情况:
         */
        if (this.arr == null || this.arr.length == 0 || this.num < 0) {
            this.count = 0;
            return;
        }
        this.count = 0;
        /**
         * 双端队列
         */
        Deque<Integer> qMax = new LinkedList<>();
        Deque<Integer> qMin = new LinkedList<>();
        /**
         * 动态窗口左边界 i
         */
        for (int i = 0, j = 0; i < this.arr.length; ++i) {
            /**
             * 动态窗口右边界 j
             */
            for (; j < this.arr.length; ++j) {
                /**
                 *
                 */
                while (!qMax.isEmpty() && this.arr[qMax.peekLast()] <= this.arr[j]) {
                    qMax.pollLast();
                }
                qMax.addLast(j);
                /**
                 *
                 */
                while (!qMin.isEmpty() && this.arr[qMin.peekLast()] >= this.arr[j]) {
                    qMin.pollLast();
                }
                qMin.addLast(j);
                /**
                 * 窗口数组不满足条件
                 */
                if (this.arr[qMax.getFirst()] - this.arr[qMin.getFirst()] > this.num) {
                    break;
                }
            }
            if (qMax.peekFirst() == i) {
                qMax.pollLast();
            }
            if (qMin.peekFirst() == i) {
                qMin.pollFirst();
            }
            this.count += j - i;
        }
    }

}
  • MaxMinusMinLENumTest.java
public class MaxMinusMinLENumTest {

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        int num = 3;
        MaxMinusMinLENum instance = new MaxMinusMinLENum(arr, num);
        int count = instance.getCount();
        System.out.println(count);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值