单调队列

单调队列(MonotonicQueue)

单调队列一般都是作为pq(PriorityQueue , 优先队列)的优化解
  • 一般使用pq做题的时候时间复杂度都是一个O(nlogn), 因为我们需要将每个元素都维护到pq中, 而且可能还需要移除, 而pq中添加元素或者是移除元素的时间复杂度都是一个O(logn)
使用单调队列解题的时间复杂度是O(n), 因为num[]中的每个元素一般都最多被添加一次, 移除一次
我个人推荐使用单调队列的时候在单调队列中维护索引, 而不要去维护值, 因为维护值可能会在有重复元素的时候出一些问题

单调队列模板大致分为以下四步:

  1. 去尾操作 : 队尾元素出队列, 当队列有新元素待入队, 需要从队尾开始入队, 删除影响队列单调性的元素, 维护队列的单调性
    • 去尾操作之后, 我们其实就可以将新的元素入队列
  2. 删头操作 : 队头元素出列. 判断队头元素是否在待求解的区间之内, 如果不在, 就将其删除
    • 如何理解删头操作 ?
    • 因为单调队列的队头元素就是待求解区间的极值
      • 如果我们维护了一个单调递减队列, 那么我们一定是每次从队头位置取出一个最大值 —> 而不是从队尾位置取出一个最小值
  3. 取解操作 : 经过去尾操作和删头操作之后, 取出队列的头元素, 就是当前区间的极值
    • 注意: 一般取解操作是可以调整顺序的, 有的题目 : 比如跳跃游戏等取解操作是放到去尾操作和删头操作之前的
模板:
public int[] monotonicQueue(int [] nums, int k) {
    int n = nums.length;
    Deque<Integer> q = new ArrayDeque<>();
    int [] res = new int[n - k + 1];
    for(int i = 0; i < n; i++) {
        //删头
        while(!q.isEmpty() && i - q.peekFirst() >= k) 
            q.pollFirst();
        //去尾
        while(!q.isEmpty() && nums[q.peekLast()] <= nums[i]) 
            q.pollLast();
        q.offer(i); //添加
        q.peekFirst(); //使用该极值最为结果 --> 取解
    }
    return res;
}
注意: 虽然说单调队列严格来说已经不是一个队列了, 因为可以从队首或者队尾移出元素, 但是我们之所以还是将其叫做单调队列肯定还是有原因的, 只是我们维护的时候需要从队尾移除元素, 但是真正取解的时候, 取极值的时候还是取队首元素 —> 也就是我们可以将从队尾移除元素的操作看做是为了能从队尾添加元素的前提, 而事实也是如此
可以看到模板中我们是先进行一个删头, 也就是先维护size为k - 1, 然后再添加元素, 保证队列长度为k
我们都知道sliding window中只能是处理都是正数, 如果是负数就不能使用sliding window了, 这个时候我们可以使用MonotonicQueue来解决
Solution974 : 和可被k整除的子数组:

可以被5整除, 就是除以5之后没有余数 :

该题目其实显然也是一个twoSum的问题, 我们维护一个前缀和数组 —> 如果两个前缀%k之后的余数是相同的, 那么就说明这两个前缀和作差的局部范围内的值是可以被k整除的 —> 但是要注意: 对于如果一个负数取余之后是一个负值, 这个时候关于其余数比较和正数就会有不同之处, 比如1, -1 , -4, 那么对应前缀和余数为1, -1, -4, 那么没有余数相同, 但是其实 -1 -4是可以被5整除的, 这里就是因为我们没有给负数修正为正数, 所以如果余数为负数, 我们就要进行一个修正 --> 1 4 1, 这样我们明显可以看到 1与1相同, 那么表示4 1两个对应的区间可以被5整除
我们如何理解总是要开始前在map中维护一个初始值?

因为我们定义的preSum[i]表示的是前i + 1项的前缀和, 那么我们如何用一个差值来表示前2项?

  • 我们可以发现是表示不了的, preSum[i]是表示的前两项, 所以我们只能是先初始一个0, 那么前两项其实就是preSum[i] - 0
Solution304 : 二维区域和检索 - 矩阵不可变

改题目其实说白了就是一个区间求和的问题, 一维数组的区间求和比较简单, 二维数组的区间求和其实还是有一定难度的

维护二维前缀和数组:
this.sum = new int[matrix.length + 1][matrix[0].length + 1];
for(int i = 0; i < matrix.length; i++) {
    for(int j = 0; j < matrix[0].length; j++) {
        //对我们的前缀和数组进行一个初始化
        sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] + matrix[i][j] - sum[i][j];
    }
}
求解区间matrix[row1] [col1] - matrix[row2] [col2]的值的和
public int sumRegion(int row1, int col1, int row2, int col2) {
    int res = sum[row2 + 1][col2 + 1] - sum[row1][col2 +1] - sum[row2 + 1][col1] + sum[row1][col1];
    return res;
}
其实就是一个集合图形的面积求解问题:
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值