【LeetCode周赛】LeetCode第375场周赛

文章介绍了LeetCode竞赛中的四个问题,涉及简单模拟、双模幂运算的优化、统计最大元素出现K次子数组以及合并区间和组合数学的应用。主要讲解了如何利用快速幂算法降低计算复杂度,以及如何通过组合数学解决问题。
摘要由CSDN通过智能技术生成

【LeetCode周赛】LeetCode第375场周赛

统计已测试设备(简单模拟题)

统计已测试设备
分析:
本题数据范围较小,所以直接按照题目意思模拟即可,遍历一遍 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,n1]的所有设备电池百分比减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 1ai,bi,ci,mi103,如果直接去计算幂运算的话,时间复杂度为 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)×(64+1)=9种不同的满足题意的子数组。
所以对于任意一个包含 k k k个最大元素的子数组,其拓展后可以有 ( l + 1 ) × ( n − r ) (l+1) \times (n-r) (l+1)×(nr)个符合题意的子数组,( 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)×(ri)个符合题意的子数组, 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)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a碟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值