【LeetCode周赛】LeetCode第375场周赛
目录
- 统计已测试设备(简单模拟题)
- 双模幂运算(快速幂)
- 统计最大元素出现至少 K 次的子数组(简单数学题)
- 统计好分割方案的数目(合并区间+组合数学+快速幂)
统计已测试设备(简单模拟题)
统计已测试设备
分析:
本题数据范围较小,所以直接按照题目意思模拟即可,遍历一遍 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)