统计已测试设备(简单模拟题)
统计已测试设备
分析:
本题数据范围较小,所以直接按照题目意思模拟即可,遍历一遍
b
a
t
t
e
r
y
P
e
r
c
e
n
t
a
g
e
s
batteryPercentages
batteryPercentages数组,如果
b
a
t
t
e
r
y
P
e
r
c
e
n
t
a
g
e
s
[
i
]
>
0
batteryPercentages[i] \gt 0
batteryPercentages[i]>0,则将
[
i
+
1
,
n
−
1
]
[i+1,n-1]
[i+1,n−1]的所有设备电池百分比减1,即
b
a
t
t
e
r
y
P
e
r
c
e
n
t
a
g
e
s
[
j
]
=
m
a
x
(
0
,
b
a
t
t
e
r
y
P
e
r
c
e
n
t
a
g
e
s
[
j
]
−
1
)
batteryPercentages[j] = max(0, batteryPercentages[j] - 1)
batteryPercentages[j]=max(0,batteryPercentages[j]−1)。暴力即可解决。
代码:
class Solution {
public:
int countTestedDevices(vector<int>& batteryPercentages) {
int n = batteryPercentages.size();
int ans = 0;
for(int i = 0; i < n; i++){
if(batteryPercentages[i] > 0){
ans++;
for(int j = i + 1; j < n; j++){
batteryPercentages[j] = max(0, batteryPercentages[j] - 1);
}
}
}
return ans;
}
};
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
双模幂运算(快速幂)
双模幂运算
分析:
根据题意,遍历
v
a
r
i
a
b
l
e
s
variables
variables数组,我们需要判断的是
(
(
a
i
b
i
%
10
)
c
i
)
%
m
i
=
=
t
a
r
g
e
t
((a_i^{b_i} \% 10)^{c_i}) \% m_i == target
((aibi%10)ci)%mi==target是否成立,又因为
1
≤
a
i
,
b
i
,
c
i
,
m
i
≤
1
0
3
1 \le a_i, b_i, c_i, m_i \le 10^3
1≤ai,bi,ci,mi≤103,如果直接去计算幂运算的话,时间复杂度为
O
(
m
2
×
n
)
O(m^2 \times n)
O(m2×n),
m
m
m表示指数
b
i
,
c
i
b_i,c_i
bi,ci的最大值,这样时间复杂度过高,那么我们使用快速幂的方法。
对于
a
b
a^b
ab来说,我们可以考虑将b拆分成二进制,以
2
11
2^{11}
211为例,11拆成二进制为
1011
1011
1011,即
8
+
2
+
1
8+2+1
8+2+1,那么
2
11
2^{11}
211可以拆分成
2
8
+
2
+
1
2^{8+2+1}
28+2+1,
1
,
2
,
8
1,2,8
1,2,8分别就是二进制位中为
1
1
1的部分。这样来做运算,只需要
O
(
log
b
)
O(\log b)
O(logb)的时间复杂度即可。具体操作见代码。
代码:
class Solution {
public:
int ksm(int a, int b, int mod){
int ans = 1, base = a;
while(b){
if(b & 1)ans = ans * base % mod;
base = base * base % mod;
b >>= 1;
}
return ans;
}
vector<int> getGoodIndices(vector<vector<int>>& variables, int target) {
vector<int>ans;
int n = variables.size();
for(int i = 0; i < n; i++){
int a = variables[i][0],b = variables[i][1], c = variables[i][2], m = variables[i][3];
if(ksm(ksm(a, b, 10), c, m) == target) ans.push_back(i);
}
return ans;
}
};
时间复杂度:
O
(
n
log
(
max
(
b
)
+
max
(
c
)
)
)
O(n \log(\max(b)+\max(c)))
O(nlog(max(b)+max(c)))
空间复杂度:
O
(
n
)
O(n)
O(n)
统计最大元素出现至少 K 次的子数组(简单数学题)
统计最大元素出现至少 K 次的子数组
分析:
根据题意,满足条件的子数组就是其中需要包含
k
k
k个最大元素,所以我们首先求出最大元素是哪一个,并且得到最大元素所在的所有位置。
对于一个有
k
k
k个最大元素的子数组,是可以任意拓展的,因为拓展是不会影响其满足题目要求的。
我们以数组
[
2
,
1
,
3
,
2
,
3
,
2
,
3
]
,
k
=
2
[2,1,3,2,3,2,3],k=2
[2,1,3,2,3,2,3],k=2为例,
3
3
3所在的下标分别为
[
2
,
4
,
6
]
[2,4,6]
[2,4,6]
所以当下标为
[
2
,
4
]
[2,4]
[2,4]的最大数为一组时,这个子数组为,
[
3
,
2
,
3
]
[3,2,3]
[3,2,3]是满足题意的,那么我可以再往左边扩展,可以是
[
1
,
3
,
2
,
3
]
,
[
2
,
1
,
3
,
2
,
3
]
[1,3,2,3],[2,1,3,2,3]
[1,3,2,3],[2,1,3,2,3],往右边扩展,可以是
[
3
,
2
,
3
,
2
]
,
[
3
,
2
,
3
,
2
,
3
]
[3,2,3,2],[3,2,3,2,3]
[3,2,3,2],[3,2,3,2,3],所以意思就是,以
[
3
,
2
,
3
]
[3,2,3]
[3,2,3]这个子数组拓展,其最左端点可以到0,最右端点可以到6,则总共有
(
2
+
1
)
×
(
6
−
4
+
1
)
=
9
(2+1) \times (6-4+1) = 9
(2+1)×(6−4+1)=9种不同的满足题意的子数组。
所以对于任意一个包含
k
k
k个最大元素的子数组,其拓展后可以有
(
l
+
1
)
×
(
n
−
r
)
(l+1) \times (n-r)
(l+1)×(n−r)个符合题意的子数组,(
l
l
l是拓展前左端点,
r
r
r是拓展前右端点,
n
n
n为数组长度,因为下标从
0
0
0开始,所以不用减1)。
那么我们直接遍历所有包含
k
k
k个最大元素的子数组,对它进行拓展计算就可以了吗?不难发现,这样会有很多次重复的计算,比如对于上述例子,若选择下标为
[
4
,
6
]
[4,6]
[4,6]的最大数为一组时,其往左拓展到
[
3
,
2
,
3
,
2
,
3
]
[3,2,3,2,3]
[3,2,3,2,3]是被重复计算过的,所以,对于拓展后的右端点的计算,我们可以将右端点设置为,该区间的下一个最大元素的前一个位置,这样我们就不会将下一个最大元素包括进来,从而减少了这种重复的计算。
所以对于任意一个包含
k
k
k个最大元素的子数组,其拓展后可以有
(
l
+
1
)
×
(
r
−
i
)
(l+1) \times (r-i)
(l+1)×(r−i)个符合题意的子数组,
i
i
i表示当前的子数组的右边界。
代码:
class Solution {
public:
long long countSubarrays(vector<int>& nums, int k) {
//先找到每一个最大数的位置,然后对每k个最大数作为一组,每一组以上一组为左边界,下下一组为右边界,可以随意构造子数组
int n = nums.size();
int mx = *max_element(nums.begin(), nums.end());
vector<int>pos;
for(int i = 0; i < n; i++){
if(nums[i] == mx) pos.push_back(i);
}
pos.push_back(n);//方便计算最后一个区间
int cnt = 0;
long long ans = 0;
//左端点随便选,右端点选到下一组的左边
for(int i = 0; i < n; i++){
if(nums[i] == mx){
cnt++;
if(cnt >= k){
int l = pos[cnt - k];
int r = pos[cnt];
//[0,l] [i + 1, r] 这些区间的数
ans += (long long)(l + 1) * (r - i);
}
}
}
return ans;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
统计好分割方案的数目(合并区间+组合数学+快速幂)
统计好分割方案的数目
分析:
将数组分割成一个或多个连续子数组,如果不存在包含了相同数字的两个子数组,则认为是一种好分割方案。
所以题意就是,相同的数字必须在一个子数组中,然后来进行分割。
根据这一点,我们可以先计算出每一个数字出现的最左端点和最右端点,对于一个数字而言,这左右端点间的所有数肯定要在一个子数组中,才能满足题意。
那多个数字呢?
每一个数字都有一个区间
[
l
,
r
]
[l,r]
[l,r],表示其最左和最右出现的位置,那么我们要将所有数字的区间合并起来,合并后的区间,必须在一个子数组中。
合并区间
所以现在的问题就变成了如何合并区间。
首先统计每个数字出现的最左边和最右边位置,遍历一遍数组即可。
对于区间合并,可以采用差分数组的方式,对于每一个区间左端点
+
1
+1
+1,右端点
−
1
-1
−1,所以只有
s
u
m
=
0
sum=0
sum=0时,这个位置才是一个完整的区间。
合并区间后,剩下的若干个区间,相互之间不包含相同的数字。
组合数学
那么其实这些区间又可以任意连续的合并,合并后也是满足题意的分割方法。所以我们可以以插板法的方法来思考。
比如对于分好的区间
[
3
]
,
[
1
,
2
,
1
]
,
[
4
]
[3],[1,2,1],[4]
[3],[1,2,1],[4]
在中间有两个空隙,对这两个位置我们可以考虑插还是不插,一共有
2
2
2^2
22种方案,不插代表这相邻区间要合并,插则代表这相邻区间不合并。
快速幂
所以计算区间数量
n
u
m
num
num,答案就是
2
n
u
m
2^{num}
2num,这里也使用快速幂,并取模。
代码:
const int mod = 1e9 + 7;
class Solution {
public:
long long ksm(int a, int b){
long long ans = 1, base = a;
while(b){
if(b & 1)ans = ans * base % mod;
base = base * base % mod;
b >>= 1;
}
return ans;
}
int numberOfGoodPartitions(vector<int>& nums) {
//先分成若干个块,也就是说,所有相同的元素都在一个块中,在这个条件下,完成分块数量最多
int n = nums.size();
unordered_map<int, pair<int, int>>mp;
for(int i = 0; i < n; i++){
int x = nums[i];
if(mp.count(x) == 0){
mp[nums[i]] = {i, i};
}
else mp[nums[i]].second = i;
}
//根据所记录的每个数的最左边和最右边的位置,合并区间
vector<int>cnt(n + 1, 0);
for(auto &[_, v]: mp){
int l = v.first, r = v.second;
cnt[l]++, cnt[r] --;
}
int sum = 0, num = 0;
for(int i = 0; i < n; i++){
sum += cnt[i];
if(sum == 0)num++;
}
return ksm(2, num - 1) % mod;
}
};
时间复杂度:
O
(
n
+
log
n
)
O(n + \log n)
O(n+logn),最坏情况下是不同的数字,则区间有n个。
空间复杂度:
O
(
n
)
O(n)
O(n)