LeetCode 1438 绝对差不超过限制的最长连续子数组

题目信息

LeetoCode地址: . - 力扣(LeetCode)

题目理解

这是一道非常好的题目,同时考察了TreeMap, 单调队列,滑动窗口。

题意就是求连续子数组中最大最小元素差值绝对值不大于k的最长长度。

滑动窗口TreeMap写法

最直观的写法就是维护一个滑动窗口,并对窗口内的元素进行排序,这样就可以随时知道窗口内的最大绝对差。

给定长度为n的nums 数组,对于任意的左右下标l 和r, 0<=l<r<=n-1 

当最大绝对差大于k时,我们便将窗口右移,并更新窗口内的元素。我们记录滑动过程中的最大长度,直到滑倒最右端。

以下面数组为例

nums = [10,1,2,4,7,2], limit = 5

滑动窗口初始左右下标l=0, r=0.

第一步,将r=0下标元素存入有序map= {10}, 因为只有一元素,最大绝对值为0. 滑块开始右移,r=r+1

第二步,将r=1下标元素存入有序map={1,10},此时最大绝对值为10-1=9,大于limit=5, 滑块左下标右移,l=1, 并将第一个元素10从有序map中移除,map={1}, 再次满足最大绝对值要求,

第三步,滑块继续右移,r=2, 存入map={1,2}. 最大长度为2.

第四步,滑块继续右移,r=3, 存入map={1,2,4}. 最大长度为3

第五步,滑块继续右移,r=4, 存入map={1,2,4,7}.此时最大绝对值为7-1=6,大于limit=5, 滑块左下标右移,l=2, 并将第二个元素1从有序map中移除,map={2,4,7}, 满足最大绝对值要求,最大长度为3.

第六步,滑块继续右移,r=5, 存入map={2,4,7,2}. 最大长度为4.

结束。

时间复杂度:O(nlogn),元素存入和移除有序Map的时间复杂度为O(logn),每个元素至多操作1次,共n次操作。

额外空间复杂度:O(n), 最眉笔的情况下,我们需要存入整个nums数组到有序Map。

class Solution {
    public int longestSubarray(int[] nums, int limit) {
        TreeMap<Integer, Integer> map = new TreeMap<>();
        int length = nums.length, l = 0, r = 0, max = 0;
        while (r < length) {
            map.put(nums[r], map.getOrDefault(nums[r], 0)+1);
            while (map.lastKey() - map.firstKey() > limit) {
                map.put(nums[l], map.getOrDefault(nums[l], 0)-1);
                if (map.get(nums[l]) == 0) {
                    map.remove(nums[l]);
                }
                l++;
            }
            max = Math.max(max, r-l+1);
            r++;
        }
        return max;
    }
}

滑动窗口+单调队列写法

上面写法的缺陷在于,存入和取出Map的时间复杂度有些高,可以将其优化为线性复杂度。

由于我们只关注滑动窗口最大值和最小值的变化,可以使用两个单调队列来保存这些信息,当滑动窗口左下标右移时,我们及时更新两个单调队列里的值便可。

类似的,我们以下面数组为例演示

nums = [10,1,2,4,7,2], limit = 5

滑动窗口初始左右下标l=0, r=0.单调递增队列minQue=[], 单调递减队列maxQue=[].

  1. l=0, r =0, 移除minQue队尾所有大于nums[r]=10的元素,并入队nums[r], minQue={10}; 类似的, 移除maxQue队尾所有小于nums[r]的元素并入队nums[r], maxQue={10}. 此时滑动窗口内的最大值是maxQue的队首10, 最小值是minQue的队尾10,最大差是0,我们无需调整滑动窗口的长度,因此最大长度为1.
  2. 滑块右扩,l=0,r=1;移除minQue队尾所有大于nums[r]=1的元素,并入队nums[r], minQue={1}; 类似的, 移除maxQue队尾所有小于nums[r]=1的元素并入队nums[r], maxQue={10,1}. 此时滑动窗口内的最大值是maxQue的队首10, 最小值是minQue的队尾1,最大差是9,我们调整滑动窗口的长度,右移l=1, 移除maxQue中的队首元素10,maxQue={1}, 此时最大差等于0, 因此最大长度保持1不变.
  3. 滑块继续右扩, l=1,r=2;更新minQue={1,2}, maxQue={2},最大差等于1,更新最大长度等于2.
  4. 滑块继续右扩,l=1,r=3; 更新minQue={1,2,4}, maxQue={4},最大差=3,最大长度=3.
  5. 滑块继续右扩,l=1,r=4; 更新minQue={1,2,4,7}, maxQue={7},最大差=6,我们开始右移l调整。右移l=2, 移除minQue队首元素1,minQue={2,4,7}, 最大差=5, 最大长度=4.
  6. 滑块继续右移,l=1,r=5, 更新minQue={2,2}, maxQue={7,2}, 最大差=5,最大长度=5
  7. 结束。

时间复杂度: O(n),没有元素重复出入队列,都是线性的

额外空间复杂度:O(n),我们维护了两个单调队列。

class Solution {
    public int longestSubarray(int[] nums, int limit) {
        LinkedList<Integer> maxQue = new LinkedList<>();
        LinkedList<Integer> minQue = new LinkedList<>();
        int length = nums.length, l = 0, r = 0, max = 0;
        while (r < length) {
            while (!maxQue.isEmpty() && maxQue.peekLast() < nums[r]) {
                maxQue.pollLast();
            }
            while (!minQue.isEmpty() && minQue.peekLast() > nums[r]) {
                minQue.pollLast();
            }
            maxQue.offer(nums[r]);
            minQue.offer(nums[r]);
            while (!maxQue.isEmpty() && !minQue.isEmpty() && maxQue.peekFirst() - minQue.peekFirst() > limit) {
                if (minQue.peekFirst() == nums[l]) {
                    minQue.pollFirst();
                }
                if (maxQue.peekFirst() == nums[l]) {
                    maxQue.pollFirst();
                }
                l++;
            }
            max = Math.max(max, r-l+1);
            r++;
        }
        return max;
    }
}

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值