P r o b l e m 6 Problem6 Problem6 构建前缀和余数最早出现的位置,移除的最短子数组长度,使得剩余元素的累加和能被 p p p整除
LeetCode 1590
使数组和能被整除
给你一个正整数数组 n u m s nums nums,请你移除最短子数组(可以为空),使得剩余元素的和能被 p p p整除。不允许将整个数组都移除。请你返回你需要移除的最短子数组的长度,如果无法满足题目的要求,返回-1。
子数组 定义为原数组中连续的一组元素。
示例1:
输入: nums = [3,1,4,2], p = 6
输出: 1
问题分析:
如果
n
u
m
s
nums
nums数组内所有元素累加和
s
u
m
sum
sum为
p
p
p的整数倍,那需要移除的最短子数组的长度自然为0。如果
n
u
m
s
nums
nums数组的累加和不能被
p
p
p整除,我们记
s
u
m
m
o
d
p
=
a
sum~mod ~p = a
sum mod p=a
要想使得移除子数组后的剩余元素累加和能被
p
p
p整除,那被我们移除的子数组的累加和,记为
s
u
m
D
r
o
p
e
d
sumDroped
sumDroped,需满足:
s
u
m
D
r
o
p
e
d
m
o
d
p
=
a
sumDroped ~ mod ~ p = a
sumDroped mod p=a
所以我们要找累加和为
s
u
m
D
r
o
p
e
d
sumDroped
sumDroped的子数组,怎么找呢?
和之前的方式一样,我们直接利用前缀和来找
s
u
m
D
r
o
p
e
d
sumDroped
sumDroped即可,会得到以下公式:
(
p
r
e
x
[
j
]
−
p
r
e
x
[
i
−
1
]
)
m
o
d
p
=
a
(prex[j] - prex[i-1]) ~mod ~p = a
(prex[j]−prex[i−1]) mod p=a
【注意】: 这里的
i
,
j
i,j
i,j都是指数字的编号,从1开始。
对上述公式进行转化求
p
r
e
x
[
i
−
1
]
prex[i-1]
prex[i−1],我们得到:
p
r
e
x
[
i
−
1
]
=
(
p
r
e
x
[
j
]
−
a
)
m
o
d
p
prex[i-1] = (prex[j] - a) ~mod ~p
prex[i−1]=(prex[j]−a) mod p
分析到这里,其实就已经可以给出解题代码了,但是我们发现,我们只看重前缀和数组
p
r
e
x
[
]
prex[]
prex[]关于
p
p
p的余数,所以我们可以将前缀和数组中的所有数变为其关于
p
p
p的余数,这样也就得到了前缀和余数数组,记为
p
r
e
x
M
o
d
P
[
]
prexModP[]
prexModP[]。这样找
p
r
e
x
[
i
−
1
]
prex[i-1]
prex[i−1]的公式就变为:
p
r
e
x
[
i
−
1
]
=
p
r
e
x
[
j
]
−
a
i
f
p
r
e
x
[
j
]
>
=
a
p
r
e
x
[
i
−
1
]
=
p
r
e
x
[
j
]
−
a
+
p
i
f
p
r
e
x
[
j
]
<
a
prex[i-1] = prex[j] - a ~~if ~prex[j] >= a \\ prex[i-1] = prex[j] - a + p ~~ if~ prex[j] <a
prex[i−1]=prex[j]−a if prex[j]>=aprex[i−1]=prex[j]−a+p if prex[j]<a
把
i
f
if
if去掉,就等价于
p
r
e
x
[
i
−
1
]
=
(
p
r
e
x
[
j
]
−
a
+
p
)
m
o
d
p
prex[i-1] = (prex[j] - a + p) ~ mod ~ p
prex[i−1]=(prex[j]−a+p) mod p
解决代码:
int solutionProblem6(int nums[], int N, int p) {
int prexModP[N + 1];
prexModP[0] = 0;
unordered_map<int, int> map; // prex[i-1] : 出现的最晚位置
map[0] = 0;
// 先找出sum%p == a来
for (int j = 0; j < N; j++) {
prexModP[j + 1] = (prexModP[j] + nums[j]) % p;
}
int a = prexModP[N];
int result = INT_MAX; // 存储结果
for (int j = 0; j < N; j++) {
map[prexModP[j + 1]] = j + 1;
int aim = (prexModP[j + 1] - a + p) % p;
if (map.count(aim) != 0) {
result = min(result, j + 1 - map[aim]);
}
}
return result == N ? -1 : result;
}
【提醒】for
循环的
j
j
j指的是 被删除数组 的最右下标,对应的位置编号是
j
+
1
j+1
j+1,我们的思路是这样的:
遍历每个位置
j
j
j,在每次遍历时去寻找有没有
p
r
e
x
[
i
−
1
]
prex[i-1]
prex[i−1],满足
p
r
e
x
[
i
−
1
]
=
(
p
r
e
x
[
j
]
−
a
+
p
)
m
o
d
p
prex[i-1] = (prex[j] - a + p) ~ mod ~ p
prex[i−1]=(prex[j]−a+p) mod p
找到则更新result
,如果最终的result
是数组长度,说明我们只有删除整个数组才能使得剩余元素,也就是0,被
p
p
p整除,这时我们就要返回-1。
P r o b l e m 7 Problem7 Problem7 构建奇偶状态 最早出现的位置,每个元音包含偶数次的最长子串长度
LeetCode
1371 每个元音包含偶数次的最长字符串
给你一个字符串s
,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即‘a’, ‘e’, ‘i’, ‘o’, ‘u’在子字符串中都恰好出现了偶数次。
示例 1:
输入:s = "eleetminicoworoep"
输出:13
解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。
示例 2:
输入:s = "leetcodeisgreat"
输出:5
解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。
示例 3:
输入:s = "bcbcbc"
输出:6
解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。
问题分析:
这道题目并不能直接转换成累加和的形式,所以前缀和是用不了了,我们得考虑其他方式来构建前缀信息。
同学们不难想到,奇数+1就变成了偶数,偶数+1就变成了奇数,这可以用异或运算来表示元音个数在奇数和偶数之间的转换。有五个元音,我们就用五个位来表示,‘_ _ _ _ _’。五个为分别对应‘a’, ‘e’, ‘i’, ‘o’, ‘u’,其中0表示元音字母出现次数为偶数,1表示出现字母为奇数。
现在我们假设符合条件的最长子串的最右下标为
j
j
j,为了保证子串更长,那我们就要尽量使子串的最左下标
i
i
i越小越好,同时还需要满足s[i,...,j]
中元音字母出现次数为偶数。
很好,现在只需要弄明白
j
j
j和
i
i
i之间的关系就好了。如果s[0,...,j]
中的‘a’出现了奇数次,那我们就要保证s[0,...,i-1]
也出现奇数次,并且
i
−
1
i-1
i−1要尽可能地小。‘e’, ‘i’, ‘o’, ‘u’也是如此。我们借助之前的”五个位“进行分析,
如果s[0,...,j]
中各元音字母的个数为’10011‘,那么我们就要找最小的
i
−
1
i-1
i−1,并使其满足s[0,...,i-1]
中各元音字母的个数为’10011‘。
分析到现在,我们总结一下:
重点分析:
每个子数组s[0,...,m]
都有一个状态‘_ _ _ _ _’来存储数组中各元音的奇偶数,对于状态为‘aabbb’(a,b = 0 或 1)的子数组s[0,...,j]
,我们要找到
i
−
1
i-1
i−1最小的并且状态同样为‘aabbb’的子数组s[0,...,i-1]
,这样就能获得最长的 每个元音字母包含偶数次 的子数组s[i,...,j]
。
所以此问题的哈希表应当是**{‘_ _ _ _ ’: 出现的最早位置 }**,但是状态‘ _ _ _ _’应该是五位的二进制数,我们直接用32位的int去表示,会浪费很多空间,同时哈希表也占挺大空间的。既然就五个位,并且【键】状态 对应的【值】是出现的最早位置,那我就直接用int map[32]
去存储前缀信息,这样非常方便,如果哈希表的键是int型数据并且连续的话,我推荐大家使用int []
去存储前缀信息。
解决代码:
//将字符变成相应的数
// 'a' -> 4, 'e' -> 3, 'i' -> 2, 'o' -> 1, 'u' -> 0, else -> -1
int move(char a) {
switch (a)
{
case 'a': return 4;
case 'e': return 3;
case 'i': return 2;
case 'o': return 1;
case 'u': return 0;
default:
return -1;
}
}
int solutionProblem6(string s) {
int map[32];
fill(map, map + 32, -2); //-2表示此状态从未出现过
map[0] = -1; //一个元素都不考虑时,位置下标可以看成-1,其对应的状态自然是 '00000',也就是0
int result = 0; //存储结果
int status = 0; //存储s[0,...,j]当前状态
for (int j = 0;j < s.length();j++) {
int m = move(s[j]);
if (m != -1) {
status ^= (1 << m);
//更新map
if (map[status] == -2) map[status] = j;
}
//寻找状态同为status的s[0,...,i-1]
if (map[status] != -2) {
result = max(result, j - map[status]);
}
}
return result;
}