【LeetCode周赛】LeetCode第373场周赛

LeetCode第373场周赛

循环移位后的矩阵相似检查

循环移位后的矩阵相似检查
分析:
简单模拟
这道题目就是一个简单的模拟题,直接按照题目意思进行判断即可。不难发现,其实左移,右移后如果初始矩阵和最终矩阵完全相同,那么左移,右移是等价的,比如对于下标为j的数,右移后相等即 m a t [ i ] [ j ] = = m a t [ i ] [ j + k ] mat[i][j] == mat[i][j+k] mat[i][j]==mat[i][j+k],对于下标为j+k的数,左移后相等即 m a t [ i ] [ j + k ] = = m a t [ i ] [ j ] mat[i][j + k] == mat[i][j] mat[i][j+k]==mat[i][j]。所以二者等价。

代码:

class Solution {
public:
    bool areSimilar(vector<vector<int>>& mat, int k) {
        bool flag = true;
        int n =  mat.size(), m =  mat[0].size();
        k %= m;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                int v = (j + k) % m;
                if(mat[i][v] == mat[i][j])continue;
                flag = false;
            }
        }
        return flag;
    }
};

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

统计美丽子字符串 I

统计美丽子字符串 I
分析:
维护前缀和
对于这个题目,因为数据范围较小,所以可以使用 O ( n 2 ) O(n^2) O(n2)的算法。如果直接暴力枚举每一个子字符串,再来判断是否是美丽子字符串的话,时间复杂度为 O ( n 3 ) O(n^3) O(n3),那么我们可以在判断是否是美丽子字符串上来优化。
如何判断一个字符串是美丽的呢?我们可以提前维护一个前缀的元音,辅音字母的数量,那么可以在 O ( 1 ) O(1) O(1)的时间复杂度下,得到一个区间的元音,辅音数量,再按照题目意思进行判断即可

代码:

class Solution {
public:
    int beautifulSubstrings(string s, int k) {
        int n = s.size();
        //预处理一个前缀的元音,辅音数量
        vector<int>pre1(n + 1, 0), pre2(n + 1, 0);
        for(int i = 0; i < n ; i++){
            if(s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u')pre1[i + 1] = pre1[i] + 1, pre2[i + 1] = pre2[i];
            else pre2[i + 1] = pre2[i] + 1, pre1[i + 1] = pre1[i];
        }
        int cnt = 0;
        for(int l = 0; l < n ; l++){
            for(int r = l + 1; r < n; r ++){
                int a = pre1[r + 1] - pre1[l];
                int b = pre2[r + 1] - pre2[l];
                if(a == b && (a * b) % k == 0)cnt++;
            }
        }
        return cnt;
    }
};

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n 2 ) O(n^2) O(n2)

交换得到字典序最小的数组

交换得到字典序最小的数组
分析:
排序+分组维护
分析题意我们可以知道,对于两两之间距离在limit范围内的两个数,我们将它们添加进一个组里面,最终可以得到若干两两相距在limit范围内的数。比如 l i m i t = 2 limit=2 limit=2,对数组 [ 1 , 5 , 3 , 9 , 8 ] [1,5,3,9,8] [1,5,3,9,8] [ 1 , 3 , 5 ] [1,3,5] [1,3,5] [ 8 , 9 ] [8,9] [8,9]分别为一组。这样的每一组里面的所有数字,都是可以任意交换位置的(不能直接交换的数也可以间接的交换)。所以本题,我们的目的就是要得到若干个这样的组,对每个组里面按照从小到大的顺序排序,就是最终的结果。
具体要怎么做呢?
首先我们可以维护一个 q q q数组,它代表的是 n u m s nums nums数组的值,从小到大排序后,原来的下标的序列。
然后我们遍历这个序列,每一次处理一段数字,这一段数字要求满足相邻两个数之间的距离 ≤ l i m i t \le limit limit
比如对于数组 [ 1 , 7 , 6 , 18 , 2 , 1 ] [1,7,6,18,2,1] [1,7,6,18,2,1],其 q q q数组为 [ 0 , 5 , 4 , 2 , 1 , 3 ] [0,5,4,2,1,3] [0,5,4,2,1,3]。处理的第一段数字为 [ 0 , 5 , 4 ] [0,5,4] [0,5,4],即下标为0,5,4的三个数。那么对这三个数,直接按照从小到大的顺序放在0,4,5的三个位置即可。因为只有这一段数字里面的位置可以任意交换。可以用set来存下来这几个下标,存入后是按照顺序排列的,然后直接放入这几个数字即可。

代码:

class Solution {
public:
    vector<int> lexicographicallySmallestArray(vector<int>& nums, int limit) {
        //在limit范围内,可以构成多个闭环,每一个闭环按照顺序排序即可
        int n = nums.size();
        vector<int>q(n);
        iota(q.begin(), q.end(), 0);
        sort(q.begin(), q.end(),
            [&](int i, int j){return nums[i] < nums[j] ;});
        //处理每一个limit范围
        vector<int>ans(n);
        for(int l = 0; l < n; ){
            int r = l + 1;
            set<int>p;
            p.insert(q[l]);
            while(r < n && nums[q[r]] - nums[q[r - 1]] <= limit){
                p.insert(q[r]);
                r++;
            }
            // cout<<l<<" "<<r<<endl;
            //l~r是一个limit范围内的一些数字,这里面的数字可以随意交换位置
            auto it = p.begin();
            for(int i = l; i < r; i++, it++)ans[*it] = nums[q[i]];
            l = r;
        }
        return ans;
    }
};

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

统计美丽子字符串 II

统计美丽子字符串 II

分析:
前缀和+分解质因数+哈希表
这道题是I的加强版,数据范围变大了,所以在I中使用的前缀和的 O ( n 2 ) O(n^2) O(n2)算法不适用了。
那么我们要考虑更加快速的方法。
前缀和
首先我们换一种维护前缀和的思路,不妨令元音为1,辅音为-1。则元音字母和辅音字母的数量相等即找到一个和为0的连续子数组。
则任何一个满足条件的子数组 ( j , i ] (j,i] (j,i]中,有 s u m [ i ] − s u m [ j ] = 0 sum[i] - sum[j] = 0 sum[i]sum[j]=0
分解质因数
设子数组的长度为L,由于元音辅音数量相等,所以元音辅音数量都等于 L 2 \frac{L}{2} 2L ,所以元音字母和辅音字母的数量的乘积能被k整除等价于
( L 2 ) 2   m o d   k = 0 (\frac{L}{2})^2\ mod \ k = 0 (2L)2 mod k=0

L 2   m o d   ( 4 k ) = 0 L^2\ mod \ (4k) = 0 L2 mod (4k)=0
再来考虑,如果 L 2 m o d   k ′ = 0 L^2 mod \ k' = 0 L2mod k=0时, L L L满足什么条件呢?
假设 k ’ k’ k是一个质数,假如是 3 3 3,那么 L L L只要满足是 3 3 3的倍数就行了,此时平方被我们去掉了。
如果 k ′ k' k是一个指数 p p p v v v次幂呢?

  • 如果v是偶数,比如 3 4 3^4 34,那么L只要是 3 2 3^2 32的倍数就行了;
  • 如果v是奇数,比如 3 5 3^5 35,那么L只要是 3 3 3^3 33的倍数就行了。

综上,L中至少要包含 p r p^r pr,其中 r = ⌈ e 2 ⌉ r=\left \lceil \dfrac{e}{2} \right \rceil r=2e
如果 k ′ k' k中包含多个质因数,则对每个质因数都按照上述方法求解即可,这样我们就可以得到 L L L至少是一个数的倍数的条件了,去掉了平方。
哈希表
在得到了上面的条件后, L L L必须是 k ′ k' k的倍数,那么会有什么情况出现呢?
现在的问题是,有多少个和为0的子数组,其长度是k’的倍数
对于一个子数组下标为 ( j , i ] (j,i] (j,i],长度 L = i − j L = i-j L=ij,若满足上述条件,有
( i − j )   m o d   k ′ = 0 (i-j) \ mod\ k' = 0 (ij) mod k=0
则有
i   m o d   k ′ = j   m o d   k ′ i \ mod\ k' = j \ mod\ k' i mod k=j mod k
对前缀和来说,则有
s u m [ i ] − s u m [ j ] = 0 sum[i]-sum[j]=0 sum[i]sum[j]=0

s u m [ i ] = s u m [ j ] sum[i]=sum[j] sum[i]=sum[j]
当我们同时满足 i   m o d   k ′ = j   m o d   k ′ i \ mod\ k' = j \ mod\ k' i mod k=j mod k s u m [ i ] = s u m [ j ] sum[i]=sum[j] sum[i]=sum[j]的条件时,则这个子数组是美丽子数组,那么我们可以一起用哈希表维护这两个值。
哈希表的key就是一个pair: i   m o d   k ′ i \ mod\ k' i mod k s u m [ i ] sum[i] sum[i],哈希表的 value 是这个 pair 的出现次数。
注意:在最一开始需要加入一个pair: k ‘ − 1 , 0 {k‘-1,0} k1,0,因为我们的前缀维护时,下标为 0 0 0时,计算的是第二个前缀和,第一个前缀和(就是什么都没有的时候,和为 0 0 0)。相当于在下标为 − 1 -1 1的时候计算, − 1 -1 1 k ’ − 1 k’-1 k1同余,则最开始加入其中。
代码:

class Solution {
public:
    int get_base(int k){
        //分解质因子
        int ans = 1;
        for(int i = 2; i * i <= k; i++){
            int cnt = 0;
            while(k % i == 0){
                cnt++;
                k /= i;
            }
            ans *= pow(i, (cnt + 1)/ 2);
        }
        if(k > 1)ans *= k;
        return ans;
    }
    long long beautifulSubstrings(string s, int k) {
        //根据条件(L/2)^2 % k == 0 即 L^2 %(4k) == 0 先判断L要满足什么样的条件
        k = get_base(4 * k);
        map<pair<int, int>, int>cnt;
        int n = s.size();
        long long ans = 0;
        int sum = 0;//计算前缀 sum[-1] = 0 下标-1就是表示没有数字的时候的前缀和
        int flag = 1 | (1 << ('e' - 'a')) | (1 << ('i' - 'a')) | (1 << ('o' - 'a')) | (1 << ('u' - 'a'));
        cnt[{k - 1, 0}]++; //加入和-1同余的k-1,-1下标相当于前缀和数组中什么都没有的情况,此时前缀和为0
        for(int i = 0; i < n; i++){
            sum += ((1 << (s[i] - 'a')) & flag) > 0 ? 1 : -1;
            ans += cnt[{i % k, sum}]++;
        }
        return ans;
    }
};

时间复杂度: O ( n + k ) O(n+\sqrt k) O(n+k )
空间复杂度: O ( n ) O(n) O(n)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

a碟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值