1717-删除子字符串的最大得分
给你一个字符串 s
和两个整数 x
和 y
。你可以执行下面两种操作任意次。
- 删除子字符串
"ab"
并得到x
分。- 比方说,从
"c **ab** xbae"
删除ab
,得到"cxbae"
。
- 比方说,从
- 删除子字符串
"ba"
并得到y
分。- 比方说,从
"cabx **ba** e"
删除ba
,得到"cabxe"
。
- 比方说,从
请返回对 s
字符串执行上面操作若干次能得到的最大得分。
示例 1:
**输入:** s = "cdbcbbaaabab", x = 4, y = 5
**输出:** 19
**解释:**
- 删除 "cdbcbbaaa **ba** b" 中加粗的 "ba" ,得到 s = "cdbcbbaaab" ,加 5 分。
- 删除 "cdbcbbaa **ab** " 中加粗的 "ab" ,得到 s = "cdbcbbaa" ,加 4 分。
- 删除 "cdbcb **ba** a" 中加粗的 "ba" ,得到 s = "cdbcba" ,加 5 分。
- 删除 "cdbc **ba** " 中加粗的 "ba" ,得到 s = "cdbc" ,加 5 分。
总得分为 5 + 4 + 5 + 5 = 19 。
示例 2:
**输入:** s = "aabbaaxybbaabb", x = 5, y = 4
**输出:** 20
提示:
1 <= s.length <= 105
1 <= x, y <= 104
s
只包含小写英文字母。
解法一
思路和算法
为了得到最大得分,应使用贪心策略,做法如下。
当 x \ne y 时,应首先执行得分高的删除操作直到无法继续删除,然后执行得分低的删除操作直到无法继续删除。
当 x = y 时,可以按任意顺序执行两种操作。
为方便处理,将 x = y 和 x > y 归入同一种情况,因此有 x \ge y 和 x < y 的两种情况,贪心策略分别如下。
当 x \ge y 时,首先删除字符串中的所有
ab",然后删除字符串中的所有
ba”。当 x < y 时,首先删除字符串中的所有
ba",然后删除字符串中的所有
ab”。
贪心策略的正确性说明如下。
每次操作无论是删除 ab" 还是删除
ba”,都会减少一个 a' 和一个
b’,且剩余字符的相对顺序不变。如果被删除的 ab" 前后都有字母且前后的字母都是 `a' 或 `b',则删除
ab” 之后,其前后的字母变成相邻,可能有以下情况。
如果前后的字母是
aa" 或
bb”,则在删除ab" 之前的 4 个相邻字符是
aaba” 或babb",无论是删除
ab” 还是删除ba" 都会剩余两个相同字母,无法一次删除两个相同字母,因此在删除
ab” 的前后,总删除次数不变。如果前后的字母是
ab",则在删除
ab” 之前的 4 个相邻字符是aabb",在删除
ab” 之后可以一次删除前后的字母ab",使用 2 次操作删除 4 个字符,因此在删除
ab” 的前后,总删除次数不变。如果前后的字母是
ba",则在删除
ab” 之前的 4 个相邻字符是baba",在删除
ab” 之后可以一次删除前后的字母ba",使用 2 次操作删除 4 个字符,另一种使用 2 次操作删除 4 个字符的方法是每次删除一个
ba”,因此在删除 ``ab” 的前后,总删除次数不变。
因此每次删除任意位置的 ab" 或
ba” 之后,总删除次数都不变。为了得到最大得分,应将得分高的删除操作次数最大化。
计算最大得分时,可以使用栈模拟删除操作,做法如下。
删除 ``ab” 的过程中,对于每个遍历到的字符,执行如下操作。
如果当前字符是
b' 且栈顶字符是
a’,则需要删除当前的 ``ab”,将栈顶字符 `a’ 出栈,将得分加 x。否则,将当前字符入栈。
删除 ``ba” 的过程中,对于每个遍历到的字符,执行如下操作。
如果当前字符是
a' 且栈顶字符是
b’,则需要删除当前的 ``ba”,将栈顶字符 `b’ 出栈,将得分加 y。否则,将当前字符入栈。
执行两轮删除操作之后,即可得到最大得分。
实现方面,可以使用 StringBuffer 或 StringBuilder 类型的可变字符串对象代替栈,可变字符串的左端为栈底,右端为栈顶,拼接和删除字符都在栈顶操作。
代码
1 | class Solution { |
1 | public class Solution { |
复杂度分析
时间复杂度:O(n),其中 n 是字符串 s 的长度。需要遍历字符串 s 两次模拟删除操作,每次遍历的时间是 O(n)。
空间复杂度:O(n),其中 n 是字符串 s 的长度。每次遍历都需要创建一个长度为 O(n) 的 StringBuffer 或 StringBuilder 类型的对象。
解法二
思路和算法
考虑字符串 s 中的每个只包含 a' 和
b’ 的最长子串,最长字串的含义如下:如果字符串 s 中的所有字符都是 a' 或
b’,则 s 为只包含 a' 和
b’ 的最长子串;如果字符串 s 中有不是 a' 或
b’ 的字符,则以不是 a' 或
b’ 的字符作为分界将 s 分成多个子串,每个用 s 的边界或者字符边界划分的子串都是只包含 a' 和
b’ 的最长子串。
对于每个最长子串,从左到右遍历,遍历过程中计算该最长子串的最大得分。如果 x \ge y 则先考虑删除 ab" 后考虑删除
ba”,如果 x < y 则先考虑删除 ba" 后考虑删除
ab”。
当 x \ge y 时,先考虑删除 ``ab”。从左到右遍历的过程中维护 a' 的个数 count}_1 和
b’ 的个数 count}_2,对于遍历到的每个字符,执行如下操作。
如果当前字符是 `a’,则将 count}_1 加 1。
如果当前字符是
b' 且 count}_1 > 0,则前面一定存在字符
a’ 可以和当前字符 `b’ 组成一个ab",删除这个
ab”,将 count}_1 减 1 并将得分加 x。如果当前字符是 `b’ 且 count}_1 = 0,则不能删除 ``ab”,将 count}_2 加 1。
遍历过程中,每个 b' 都会和前面的
a’ 配对并删除。遍历结束之后,剩余的字母一定满足 b' 在前,
a’ 在后,此时不能继续删除 ab",因此删除
ba”,删除 ``ba” 的次数为 \min(\textit{count}_1, \textit{count}_2),将得分加 y \times \min(\textit{count}_1, \textit{count}_2)。此时即可得到当前最长子串的最大得分。
当 x < y 时,先考虑删除 ba",后考虑删除
ab”,使用相同的方法计算当前最长子串的最大得分。
上述做法为使用常数空间的贪心策略。由于优先考虑得分高的删除操作,且删除次数最大化,因此上述贪心策略可以得到最大得分。
实现方面,从左到右遍历字符串 s,对于遍历到的每个字符,如果当前字符为字符串的末尾字符或者当前字符的后一个字符不是 a' 或
b’,则当前字符为一个最长字串的末尾字符,计算当前最长字串的最大得分。
代码
1 | class Solution { |
1 | public class Solution { |
复杂度分析
时间复杂度:O(n),其中 n 是字符串 s 的长度。需要遍历字符串 s 一次计算最大得分。
空间复杂度:O(1)。