• LeetCode 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit (绝对差不超过限制的最长连续子数组)


    给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。

    如果不存在满足条件的子数组,则返回 0 。

    示例 1:

    输入:nums = [8,2,4,7], limit = 4
    输出:2 
    解释:所有子数组如下:
    [8] 最大绝对差 |8-8| = 0 <= 4.
    [8,2] 最大绝对差 |8-2| = 6 > 4. 
    [8,2,4] 最大绝对差 |8-2| = 6 > 4.
    [8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
    [2] 最大绝对差 |2-2| = 0 <= 4.
    [2,4] 最大绝对差 |2-4| = 2 <= 4.
    [2,4,7] 最大绝对差 |2-7| = 5 > 4.
    [4] 最大绝对差 |4-4| = 0 <= 4.
    [4,7] 最大绝对差 |4-7| = 3 <= 4.
    [7] 最大绝对差 |7-7| = 0 <= 4. 
    因此,满足题意的最长子数组的长度为 2

    比赛时这破题真就卡了一个小时= = 然后第四题没来记得看= = 我好菜啊

    解法1、树状数组/线段树 求区间极值 + 二分

    树状数组求区间最大最小值 logn 二分长度然后枚举起点 时间复杂度 O(logn * logn * n)

    线段树 太久没写了 练下手。不过这题比较简单,没有更新,没有什么pushup pushdown的操作

    #define lson (o<<1)
    #define rson (o<<1|1)
    #define mid ((l+r)>>1)
    
    const int N = 100005;
    int max_tr[N * 3], min_tr[N * 3];
    
    class Solution {
    public:
        int longestSubarray(vector<int>& nums, int limit) {
            int n = nums.size();
            build(1, 1, n, nums);        
            int l = 1, r = n;
            int ans = 0;
            // 二分长度
            while (l <= r) {
                int m = (l + r) >> 1;
                bool f = false;
                for (int i = 0, j; (j = i + m - 1) < n; i++) {
                    int maxv = query_max(1, 1, n, i + 1, j + 1);
                    int minv = query_min(1, 1, n, i + 1, j + 1);
                    if (maxv - minv <= limit) {
                        f = true;
                        break;
                    }
                }
                if (f) ans = m, l = m + 1;
                else r = m - 1;
            }
            return ans;
        }
        void build(int o, int l, int r, vector<int>& nums) {
            if (l == r) {
                max_tr[o] = min_tr[o] = nums[l - 1];
                return ;
            }
            build(lson, l, mid, nums);
            build(rson, mid + 1, r, nums);
            pushup(o);
        }
        void pushup(int o) {
            max_tr[o] = max(max_tr[lson], max_tr[rson]);
            min_tr[o] = min(min_tr[lson], min_tr[rson]);
        }
        int query_min(int o, int l, int r, int L, int R) {
            if (l >= L && r <= R) return min_tr[o];
            int res = INT_MAX;
            if (L <= mid) res = min(res, query_min(lson, l, mid, L, R));
            if (R > mid) res = min(res, query_min(rson, mid + 1, r, L, R));
            return res;
        }
        int query_max(int o, int l, int r, int L, int R) {
            if (l >= L && r <= R) return max_tr[o];
            int res = 0;
            if (L <= mid) res = max(res, query_max(lson, l, mid, L, R));
            if (R > mid) res = max(res, query_max(rson, mid + 1, r, L, R));
            return res;
        }
    };
    View Code

    树状数组 求区间极值我一直不会。。。so。。比赛时现搜的代码,然后找了一个错误的,调了半个小时。。。又换了一个博客才AC。QAQ

    const int N = 100005;
    int arrmi[N], arrmx[N];
    
    class Solution {
    public:
        int longestSubarray(vector<int>& nums, int limit) {
            int n = nums.size();
            init(nums);
            int l = 1, r = n;
            int ans = 0;
            // 二分长度
            while (l <= r) {
                int m = (l + r) >> 1;
                bool f = false;
                for (int i = 0, j; (j = i + m - 1) < n; i++) {
                    int maxv = querymx(i + 1, j + 1, nums);
                    int minv = querymi(i + 1, j + 1, nums);
                    if (maxv - minv <= limit) {
                        f = true;
                        break;
                    }
                }
                if (f) ans = m, l = m + 1;
                else r = m - 1;
            }
            return ans;
        }
        void init(vector<int>& nums) {  
            for(int i = 1; i <= nums.size(); ++i) arrmi[i] = INT_MAX;
            for(int i = 1; i <= nums.size(); ++i)  
                for(int j = i; j <= nums.size() && arrmi[j] > nums[i - 1]; j += lowbit(j))
                    arrmi[j] = nums[i - 1];
    
            memset(arrmx, 0, sizeof arrmx);
            for(int i = 1; i <= nums.size(); ++i)  
                for(int j = i; j <= nums.size() && arrmx[j] < nums[i - 1]; j += lowbit(j))
                    arrmx[j] = nums[i - 1];
        }  
        int querymx(int L, int R, vector<int>& nums) {  
            int res = 0;  
            for (--L; L < R; ){  
                if (R - lowbit(R) >= L) {
                    res = max(res, arrmx[R]); R -= lowbit(R);
                } else {
                    res = max(res, nums[R - 1]);
                    --R;
                }
            }  
            return res;  
        }
        int querymi(int L, int R, vector<int>& nums) {  
            int res = INT_MAX;  
            for (--L; L < R; ){  
                if (R - lowbit(R) >= L) {
                    res = min(res, arrmi[R]); R -= lowbit(R);
                } else {
                    res = min(res, nums[R - 1]);
                    --R;
                }
            }  
            return res;  
        }
        int lowbit(int x) { return x & -x; }
    };
    View Code

    树状数组(1032 ms)比线段树(1944 ms)快一倍

    解法2、multiset + 滑动窗口

    我只能说STL是真的好用,但是我不太会用啊。。。。

    multiset 能在 logn 的事件复杂度求出最大最小值,也能在 O(logn) 的复杂度增加删除元素

    class Solution {
    public:
        int longestSubarray(vector<int>& nums, int limit) {
            int n = nums.size();
            multiset<int> st;
            int l = 0, r = 0, ans = 0;
            while (r < n) {
                st.insert(nums[r++]);
                while (*st.rbegin() - *st.begin() > limit) {
                    st.erase(st.find(nums[l++]));
                }
                ans = max(ans, r - l);
            }
            return ans;
        }
    };

    md 10行代码,我看着我比赛写的100行代码真的好气。。然后只需要 260 ms。。。

    解法3、双端队列 + 滑动窗口

    class Solution {
    public:
        int longestSubarray(vector<int>& nums, int limit) {
            int n = nums.size();
            deque<int> deq; // 降序队列 记录最大值
            deque<int> inq; // 升序队列 记录最小值
            int l = 0, r = 0, ans = 0;
            while (r < n) {
                int x = nums[r];
                while (deq.size() && nums[deq.back()] <= x) {
                    deq.pop_back();
                }
                deq.push_back(r);
                while (inq.size() && nums[inq.back()] >= x) {
                    inq.pop_back();
                }
                inq.push_back(r);
                while (nums[deq.front()] - nums[inq.front()] > limit) {
                    l++;
                    if (deq.front() < l) deq.pop_front();
                    if (inq.front() < l) inq.pop_front();
                }
                ans = max(ans, r - l + 1);
                r++;
            }
            return ans;
        }
    };

    感觉这种解法更妙一些,应该是 O(n) 的时间复杂度吧。132 ms 我之前应该是写过这种题,但是应该是很久以前了,只有浅浅的印象==

    用两个双端队列记录每一个对最大最小值有贡献的位置

    比如输入 [8,2,4,7] 4

    初始化,l=0, r=0

    deq [0] inq [0] // 都是[8]

    最大值-最小值 = 8-8 = 0 <= 4 所以 子数组 [0,0] 长度为 1

    右端向右移动,r=1

    deq [0,1] inq [1] // 下标对应值 deq [8,2] inq [2]

    对于inq,要知道计算是从前到后的 1 这个位置比 0 小,所以 0 这个位置对于最小值是没有贡献的,如果你想向前移动到最小值变大,一定要移动到 1 后面。这就是 inq 记录的下标的意义。

    而对于 deq 我们记录的是对最大值右贡献的位置,如果你想最大值变小,那么 由 0->1 是由变化的,所以要留下 0 这个位置。

    此时最大值 8 最小值 2,8-2>4 所以 l 应该向右移动。

    l 向右移动 1,此时 l=1 所以 deq和inq中小于1的下标都应该被删除。

    deq [1] inq [1] 最大值-最小值=1-1=0

    此时子数组[1,1] 长度为1

    r继续向右移动,r=2

    deq[2] inq[1,2] // 对于值 deq[4]  inq[2,4]

    最大值-最小值 = 4-2 = 2<=4

    此时子数组[1,2] 长度为2

    r继续向右移动,r=3

    deq[3] inq[1,2,3] // 对于值 deq[7]  inq[2,4,7]

    最大值-最小值 = 7-2 = 5>4,所以l需要向右移动

    l=2, deq和inq中小于2的下标都应该被删除。

    deq[3] inq[2,3] // 对于值 deq[7]  inq[4,7]

    最大值-最小值 = 7-4 = 3<=4

    此时子数组[2,3] 长度为2

    所以最长的子数组长度为2。

  • 相关阅读:
    java IO流详解
    java设计模式之单例模式(几种写法及比较)
    JS定时刷新页面及跳转页面
    遍历map的四种方法
    String 中去掉空格
    TSP问题_遗传算法(STL大量使用)
    无向图的深度优先生成树和广度优先生成树
    Prim算法求最小生成树
    哈夫曼编码_静态库
    中序线索化二叉树
  • 原文地址:https://www.cnblogs.com/wenruo/p/12827689.html
Copyright © 2020-2023  润新知