1562. 查找大小为 M 的最新分组(动态维护区间)
题目描述
给出一个非负整数的数据流 a1,a2,…an,…a1,a2,…an,…,请将它们动态地维护成一系列不相交的区间。
思考题: 假设有非常多的区间合并操作,并且区间总数远小于所有数的个数时,该怎么做?
样例
给定数据流:1, 3, 7, 2, 6, ..., 动态得到的区间是:
[1, 1]
[1, 1], [3, 3]
[1, 1], [3, 3], [7, 7]
[1, 3], [7, 7]
[1, 3], [6, 7]
算法
(平衡树) addNum: O(logn), getIntervals: O(n)
我们用 map<int,int> L, R 来动态维护所有区间,L可以从区间右端点索引到左端点,R可以从区间左端点索引到右端点。
举例来说,假设有个区间是[x, y],则L[y] = x并且R[x] = y。
对于addNum(val)操作:
我们先判断val是否已经包含在某个区间中。具体来说,先用upper_bound(val)找出最后一个左端点小于等于val的区间,然后判断该区间的右端点是否大于等于val。
如果val不包含在任何区间中,则分别判断val-1和val+1是否存在:
如果都存在,则合并左右区间,合并方式:R[L[val - 1]] = R[val + 1],L[R[val + 1]] = L[val - 1],然后将R[val+1]和L[val-1]删除;
如果只有一边存在,则将val插入该区间中;
如果都不存在,则将val表示成一个单独的区间;
对于getIntervals()操作,直接输出所有区间即可。
时间复杂度分析:对于addNum操作,只涉及常数次平衡树的增删改查操作,所以时间复杂度是 O(logn)。对于getIntervals操作,需要将整棵平衡树遍历一遍,所以时间复杂度是 O(n)
/** * Definition for an interval. * struct Interval { * int start; * int end; * Interval() : start(0), end(0) {} * Interval(int s, int e) : start(s), end(e) {} * }; */ class SummaryRanges { public: /** Initialize your data structure here. */ map<int,int>L, R; SummaryRanges() { } void addNum(int val) { if (R.size()) { auto it = R.upper_bound(val); if (it != R.begin()) { -- it; if (it->second >= val) return; } } int right = R.count(val + 1), left = L.count(val - 1); if (left && right) { R[L[val - 1]] = R[val + 1]; L[R[val + 1]] = L[val - 1]; R.erase(val + 1), L.erase(val - 1); } else if (right) { L[R[val + 1]] = val; R[val] = R[val + 1]; R.erase(val + 1); } else if (left) { R[L[val - 1]] = val; L[val] = L[val - 1]; L.erase(val - 1); } else { R[val] = val; L[val] = val; } } vector<Interval> getIntervals() { vector<Interval> res; for (auto &p : R) res.push_back(Interval(p.first, p.second)); return res; } }; /** * Your SummaryRanges object will be instantiated and called as such: * SummaryRanges obj = new SummaryRanges(); * obj.addNum(val); * vector<Interval> param_2 = obj.getIntervals(); */ 作者:yxc 链接:https://www.acwing.com/solution/content/373/ 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
例二:
给你一个数组 arr ,该数组表示一个从 1 到 n 的数字排列。有一个长度为 n 的二进制字符串,该字符串上的所有位最初都设置为 0 。
在从 1 到 n 的每个步骤 i 中(假设二进制字符串和 arr 都是从 1 开始索引的情况下),二进制字符串上位于位置 arr[i] 的位将会设为 1 。
给你一个整数 m ,请你找出二进制字符串上存在长度为 m 的一组 1 的最后步骤。一组 1 是一个连续的、由 1 组成的子串,且左右两边不再有可以延伸的 1 。
返回存在长度 恰好 为 m 的 一组 1 的最后步骤。如果不存在这样的步骤,请返回 -1 。
示例 1:
输入:arr = [3,5,1,2,4], m = 1
输出:4
解释:
步骤 1:"00100",由 1 构成的组:["1"]
步骤 2:"00101",由 1 构成的组:["1", "1"]
步骤 3:"10101",由 1 构成的组:["1", "1", "1"]
步骤 4:"11101",由 1 构成的组:["111", "1"]
步骤 5:"11111",由 1 构成的组:["11111"]
存在长度为 1 的一组 1 的最后步骤是步骤 4 。
示例 2:
输入:arr = [3,1,5,4,2], m = 2
输出:-1
解释:
步骤 1:"00100",由 1 构成的组:["1"]
步骤 2:"10100",由 1 构成的组:["1", "1"]
步骤 3:"10101",由 1 构成的组:["1", "1", "1"]
步骤 4:"10111",由 1 构成的组:["1", "111"]
步骤 5:"11111",由 1 构成的组:["11111"]
不管是哪一步骤都无法形成长度为 2 的一组 1 。
示例 3:
输入:arr = [1], m = 1
输出:1
示例 4:
输入:arr = [2,1], m = 2
输出:2
提示:
n == arr.length
1 <= n <= 10^5
1 <= arr[i] <= n
arr 中的所有整数 互不相同
1 <= m <= arr.length
class Solution { public: vector<int> l, r; int get(int x) { return r[x] - x + 1; } int findLatestStep(vector<int>& arr, int m) { int n = arr.size(); l.resize(n + 2), r.resize(n + 2); int cnt = 0, ans = -1; for(int i=0;i<arr.size();i++){ int x=arr[i]; if(l[x-1]&&r[x+1]){ if(get(l[x-1])==m) cnt--; if(get(x+1)==m) cnt--; r[l[x-1]]=r[x+1]; l[r[x+1]]=l[x-1]; if(get(l[x-1])==m) cnt++; } else if(l[x-1]){ if(get(l[x-1])==m) cnt--; r[l[x-1]]=x,l[x]=l[x-1]; if(get(l[x-1])==m) cnt++; } else if(r[x+1]){ if(get(x+1)==m) cnt--; r[x]=r[x+1]; l[r[x+1]]=x; if(get(x)==m) cnt++; } else{ l[x]=r[x]=x; if(m==1) cnt++; } if(cnt) ans=i+1; } return ans; } };