0%

3097. 或值至少为 K 的最短子数组 II

3097.或值至少为 K 的最短子数组

题目描述:

给你一个 非负 整数数组 nums 和一个整数 k

如果一个数组中所有元素的按位或运算 OR 的值 至少k ,那么我们称这个数组是 特别的

请你返回 nums最短特别非空 子数组的长度,如果特别子数组不存在,那么返回 -1

数据范围:

$1\le n \le 2\times 10^5, 1\le nums[i] \le 10^9, 0 \le k \le 10^9$

题解:

滑动窗口:

由于存在单调性,or 运算只能越来越大。因此可以使用滑动窗口来做。

需要使用一个长度为 $32$ 的数组,维护当前窗口每一位上 $1$ 的个数。每次加入 $nums[r]$ ,更新当前窗口里面 $1$ 的个数,移除左端点 $nums[l]$ 时也要更新。

子数组 OR/AND/GCD 通用模板:

由于是 or 运算,只能越来越大,而且最多只会出现 $\log(nums[i])$ 种不同的值。因此可以直接维护当前以 $i$ 为终点的子数组中,or 值的种类。使用一个vector<pair<int, int>> orsum 表示。其中元素对 (key,val) 表示当前 orsumkey 时,最大的 leftval。这样的话 i - val + 1 就可以得到子数组 orsum >= k 时的长度了。假设已经得到了 $nums[i - 1]$ 的 orsum 数组,那么加入 $nums[i]$ 时,需要考虑将 $nums[i]$ 与里面的所有元素 or 一遍,同时需要注意去重。单调性是递减的,因为越往后,或的越少,数值只能更小。

例如 a1, a2, a3

(a1, 0)

(a1 | a2, 0), (a2, 1)

(a1 | a2 | a3, 0), (a2 | a3, 1), (a3, 2)

key 是递减的,val 是递增的。

其实也可以先运算一遍之后,直接调用去重的 unique 函数。

为了便利计算,可以先放入 (0, i),为了最后 or 的时候得到 (ai, i)

原地去重:

考虑一个递减数组,原地去重操作。

假设当前数组末尾是 nums[k],每次将需要加入的 nums[i]nums[k] 对比,如果不相同,则放到 nums[k + 1] 处;相同的话则继续往后遍历。

1
2
3
4
5
6
7
int k = 0;
for(int i = 0; i < nums.size(); ++i) {
if(nums[i] != nums[k]) {
nums[++k] = nums[i];
}
}
nums.resize(k + 1);

模板:

1
2
3
4
5
6
7
8
for(int i = 0; i < n; ++i) {
for(int j = i - 1; j >= 0; --j) {
if(nums[j] ? nums[i] == nums[j]) {
break;
}
// 统计,求最优值等。
}
}

其中会得到这样的序列

a1?a2?a3?a4, a2?a3?a4, a3?a4, a4

由于 lcm, gcd, |, & 具有单调性。当 nums[j] | nums[i] = nums[j] 时,表明,nums[i]nums[j] 的子集,又因为 nums[j] 是左侧所有元素的子集,因此 nums[i]nums[k], 0<k<=j 的子集,因此继续往前都不会有什么变化了。

同理gcd, lcm, & 也可以得到类似的结论。

而且由于单调性,所得到的序列可以进行二分。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const static long long mod = 1e9 + 7;
const long long INF_LL = 0x3f3f3f3f3f3f3f3f;
const int INF = 0x3f3f3f3f;
int getOrSum(vector<int> &nums)
{
int sum = 0;
for (int i = 0; i < 32; ++i)
{
if (nums[i] > 0)
{
sum += (1 << i);
}
}
return sum;
}
void diffVector(vector<int> &a, vector<int> &b)
{
for (int i = 0; i < 32; ++i)
{
a[i] -= b[i];
}
}
void addVector(vector<int> &a, vector<int> &b)
{
for (int i = 0; i < 32; ++i)
{
a[i] += b[i];
}
}
int minimumSubarrayLength(vector<int> &nums, int k)
{
int n = nums.size();
vector<vector<int>> cur(n, vector<int>(32));
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < 32; ++j)
{
if ((nums[i] >> j) & 1)
{
cur[i][j]++;
}
}
}
int orsum = 0;
vector<int> tmp(32);
int l = 0;
int ans = INF;
for (int r = 0; r < n; ++r)
{
addVector(tmp, cur[r]);
orsum = getOrSum(tmp);
while (orsum >= k && l <= r)
{
if (orsum >= k)
{
ans = min(ans, r - l + 1);
}
diffVector(tmp, cur[l]);
orsum = getOrSum(tmp);
++l;
}
}
return ans == INF ? -1 : ans;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
const static int maxn = 1e5 + 10;
const static int maxm = 1e5 + 10;
const static long long mod = 1e9 + 7;
const long long INF_LL = 0x3f3f3f3f3f3f3f3f;
const int INF = 0x3f3f3f3f;
int minimumSubarrayLength(vector<int> &nums, int k)
{
int n = nums.size();
int ans = INF;
vector<pair<int, int>> orsum; // orsum 表示遍历到 i 时,sum: 对应的最短子数组的左端点 left
for (int i = 0; i < n; ++i)
{
// 首先,往 i-1 中加入了一个元素,全算一次 or,递减的
int size = 0;
orsum.emplace_back(0, i);
for (int j = 0; j < orsum.size(); ++j)
{
auto &[key, val] = orsum[j];
key |= nums[i];
if (key >= k) ans = min(ans, i - val + 1);
// 看和末尾的是不是一样,一样的话已经修改了,需要修改一下second,不一样的话就是变小了,放进去
if (orsum[size].first == key) orsum[size].second = val;
else orsum[++size] = orsum[j];
}
orsum.resize(size + 1);
}
return ans == INF ? -1 : ans;
}
};