Subarray With Elements Greater Than Varying Threshold
作者:互联网
Subarray With Elements Greater Than Varying Threshold
You are given an integer array $nums$ and an integer $threshold$.
Find any subarray of $nums$ of length $k$ such that every element in the subarray is greater than $threshold / k$.
Return the size of any such subarray. If there is no such subarray, return $-1$.
A subarray is a contiguous non-empty sequence of elements within an array.
Example 1:
Input: nums = [1,3,4,3,1], threshold = 6 Output: 3 Explanation: The subarray [3,4,3] has a size of 3, and every element is greater than 6 / 3 = 2. Note that this is the only valid subarray.
Example 2:
Input: nums = [6,5,6,5,8], threshold = 7 Output: 1 Explanation: The subarray [8] has a size of 1, and 8 > 7 / 1 = 7. So 1 is returned. Note that the subarray [6,5] has a size of 2, and every element is greater than 7 / 2 = 3.5. Similarly, the subarrays [6,5,6], [6,5,6,5], [6,5,6,5,8] also satisfy the given conditions. Therefore, 2, 3, 4, or 5 may also be returned.
Constraints:
$1 \leq nums.length \leq {10}^{5}$
$1 \leq nums[i], threshold \leq {10}^9$
解题思路
因为$k$的取值范围是$1 \sim n$,因此可以枚举$k$。这里不可以用二分,因为不具有二段性。
从小到大枚举$k$,那么随着$k$的增大,$\frac{T}{k}$会逐渐减小,因此满足要求的数会逐渐增多。在增大$k$的过程中,如果发现某个数满足$nums[i] > \frac{T}{k}$,那么就把它标记出来,$i$为这个数的下标,这里的标记是指在数轴$i$这个位置打个标记,表示这个位置上有数,一开始这个数轴是空的。因此每次增加$k$的值,就逐渐会有满足要求的数加进数轴,数轴上加进来的数越来越多。每加入一个数,就判断与这个数相连的连续一段数的个数是否大于等于$k$就好了。
因此我们需要找个数据结构来实现我们的操作。一开始区间(数轴)是空的,每次往里面添加一个元素,添加元素之后维护包括当前元素所在的连续被标记过的区间长度(如上图),看一下区间的长度是多少。我们可以用并查集来实现,不过这个并查集与一般的并查集不一样。
参考修改数组。一开始所有元素都没有被标记,因此并查集初始化时每个元素都指向自己。如果某个元素被标记了,那么这个元素就指向它的下一个位置的元素。因此每个集合中除了根节点(代表元素)外,其余的元素都是被标记过的(根元素指向自己因此没被标记)。还需要维护每个集合的大小,因此每个集合中被标记的元素个数就是当前集合的大小减去$1$。
另外,因为我们是从小到大去枚举$k$,因此为了快速知道哪些数满足$nums[i] > \frac{T}{k}$(元素都是从大到小添加进来的),我们先对数组进行降序排序(数值和对应的下标进行排序,实际上我们只用到排序后这个数在原数组中的下标是多少)。
AC代码如下:
1 class Solution { 2 public: 3 vector<int> fa, idx, cnt; 4 5 int find(int x) { 6 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 7 } 8 9 int validSubarraySize(vector<int>& nums, int threshold) { 10 int n = nums.size(); 11 for (int i = 0; i <= n; i++) { 12 fa.push_back(i); 13 idx.push_back(i); // idx记录每个数在原始数组中的下标 14 cnt.push_back(1); 15 } 16 17 idx.pop_back(); // 把下标n弹出 18 sort(idx.begin(), idx.end(), [&](int a, int b) { // 根据原始数组中每个数的数值从大到小排序,最后idx就得到排序后每个数在原始数组中的下标位置 19 return nums[a] > nums[b]; 20 }); 21 22 for (int k = 1, i = 0; k <= n; k++) { 23 while (i < n && nums[idx[i]] > threshold / k) { 24 // idx[i]表示元素数组的的下标,idx[i]+1表示这个元素在原始数组的下标的下一个位置 25 cnt[find(idx[i] + 1)] += cnt[idx[i]]; 26 fa[idx[i]] = find(idx[i] + 1); // 当前元素指向下一个位置的元素 27 if (cnt[find(idx[i])] - 1 >= k) return k; 28 i++; 29 } 30 } 31 32 return -1; 33 } 34 };
其实不用这种并查集也是可以的。本质上是往区间加入一个元素,然后对这个元素左右两边的区间进行合并(如果可以合并的话),然后再求合并后的新的区间的长度,也就是动态维护连续的区间。用普通并查集也是可以做的。
AC代码如下:
1 class Solution { 2 public: 3 vector<int> fa, idx, cnt; 4 5 int find(int x) { 6 return fa[x] == x ? fa[x] : fa[x] = find(fa[x]); 7 } 8 9 int validSubarraySize(vector<int>& nums, int threshold) { 10 int n = nums.size(); 11 for (int i = 0; i < n; i++) { 12 fa.push_back(i); 13 idx.push_back(i); 14 cnt.push_back(0); // 这里初始化为0是为了方便表示当前元这个素还没有被标记 15 } 16 17 sort(idx.begin(), idx.end(), [&](int a, int b) { 18 return nums[a] > nums[b]; 19 }); 20 21 for (int k = 1, i = 0; k <= n; k++) { 22 while (i < n && nums[idx[i]] > threshold / k) { 23 cnt[idx[i]] = 1; // 当前元素被标记,赋值为1 24 if (idx[i] - 1 >= 0 && cnt[find(idx[i] - 1)]) { // 左边的元素所在区间已被标记,就把左边的集合与新元素合并 25 cnt[idx[i]] += cnt[find(idx[i] - 1)]; 26 fa[find(idx[i] - 1)] = idx[i]; 27 } 28 if (idx[i] + 1 < n && cnt[find(idx[i] + 1)]) { // 右边的元素所在区间已被标记,就把右边的集合与新元素合并 29 cnt[idx[i]] += cnt[find(idx[i] + 1)]; 30 fa[find(idx[i] + 1)] = idx[i]; 31 } 32 if (cnt[idx[i]] >= k) return k; 33 i++; 34 } 35 } 36 37 return -1; 38 } 39 };
参考资料
出题人表示,就是要深夜搞你们心态!力扣第82场双周赛:https://www.bilibili.com/video/BV1s94y197XG
C++匿名函数:https://www.cnblogs.com/Brickert/p/13164291.html
标签:cnt,Elements,Greater,idx,nums,int,fa,Varying,find 来源: https://www.cnblogs.com/onlyblues/p/16637247.html