循环移位后的矩阵相似检查
循环移位后的矩阵相似检查
分析:
简单模拟
这道题目就是一个简单的模拟题,直接按照题目意思进行判断即可。不难发现,其实左移,右移后如果初始矩阵和最终矩阵完全相同,那么左移,右移是等价的,比如对于下标为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
分析:
前缀和+分解质因数+哈希表
这道题是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=i−j,若满足上述条件,有
(
i
−
j
)
m
o
d
k
′
=
0
(i-j) \ mod\ k' = 0
(i−j) 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}
k‘−1,0,因为我们的前缀维护时,下标为
0
0
0时,计算的是第二个前缀和,第一个前缀和(就是什么都没有的时候,和为
0
0
0)。相当于在下标为
−
1
-1
−1的时候计算,
−
1
-1
−1和
k
’
−
1
k’-1
k’−1同余,则最开始加入其中。
代码:
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)