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。

 

posted @ 2020-05-05 12:11  我不吃饼干呀  阅读(417)  评论(0编辑  收藏  举报