本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
You are given a string s consisting only of characters 'a' and 'b'.
You can delete any number of characters in s to make s balanced. s is balanced if there is no pair of indices (i,j) such that i < j and s[i] = 'b' and s[j]= 'a'.
Return the minimum number of deletions needed to make s balanced.
Example 1:
Input: s = "aababbab"
Output: 2
Explanation: You can either:
Delete the characters at 0-indexed positions 2 and 6 ("aababbab" -> "aaabbb"), or
Delete the characters at 0-indexed positions 3 and 6 ("aababbab" -> "aabbbb").
Example 2:
Input: s = "bbaaaaabb"
Output: 2
Explanation: The only solution is to delete the first two characters.
Constraints:
1 <= s.length <= 105s[i]is'a'or'b'.
题意:给出一个仅由 'a', 'b' 组成的字符串 s ,如果任意下标对 (i, j), i < j ,都不存在 s[i] == 'b' and s[j] == 'a' ,就称 s 是平衡的。你可以删除 s 中任意数量的字符,使 s 平衡。返回最少的使 s 平衡的删除数。
解法1 枚举分割线(两次遍历)
完全不需要过多的思考。由于平衡字符串的 'a' 都在 'b' 的左边,因此一定存在一条分割线,将 s 分成前后两部分,'a' 都在前面一部分,'b' 都在后面一部分。反过来说,要删除的 'b' 都在前一部分,要删除的 'a' 都在后一部分。我们要做的就是,枚举所有
n
+
1
n +1
n+1 条分割线,计算删除次数的最小值,就是答案。
最简单的实现如下所示。第一次遍历计算 'a' 的总个数
a
a
a ,并将其作为「以下标
0
0
0 之前作为分割线、要删除全部 'a' 」的删除次数
m
i
n
D
e
l
minDel
minDel ;第二次遍历时,当前字符如果是 'a' 就令 ta 加1,并以下标
i
i
i 之后作为分割线,然后计算出前一部分中 'b' 的个数
i
+
1
−
t
a
i + 1 - ta
i+1−ta 、和后一部分中 'a' 的个数
a
−
t
a
a - ta
a−ta ,这二者之和与
m
i
n
D
e
l
minDel
minDel 比较求最小值:
class Solution {
public int minimumDeletions(String s) {
int a = 0, ta = 0, n = s.length();
for (int i = 0; i < n; ++i)
if (s.charAt(i) == 'a') ++a;
int minDel = a; // 以下标0之前作为a,b分割线,要删除全部a
for (int i = 0; i < n; ++i) { // 以下标i之后作为分割线
if (s.charAt(i) == 'a') ++ta;
int bComeBeforeA = i + 1 - ta;
// ta bComeBeforeA | a-ta n-a-bComeBeforeA
// 删除前面的bComeBeforeA个b和后面的a-ta个a
minDel = Math.min(minDel, bComeBeforeA + a - ta);
}
return minDel;
}
}
上面的写法简单易懂,但我们还可稍微优化一下。
- 去掉if语句,如第一次遍历中改为
a += 'b' - s.charAt(i);第二次遍历中改为(s.charAt(i) - 'a') * 2 - 1。从而避免因条件跳转指令而出现的CPU分支预测。 - 第一次遍历仍然是统计
s
s
s 中
'a'的个数,并将其作为 d e l del del 。只是第二次遍历时,下标 i i i 变大的过程就是分割线不断右移的过程,我们每遇到一个'a',就相当于将一个'a'纳入前一部分,从而删除'a'的次数减1;每遇到一个'b',就相当于要删除的'b'的次数加1。这样不断减少或增加删除次数,并用 a n s ans ans 统计所有 d e l del del 中的最小值。示例图(借用灵茶山艾府大佬画的图)如下:

class Solution {
public int minimumDeletions(String s) {
int del = 0, n = s.length();
for (int i = 0; i < n; ++i)
del += ('b' - s.charAt(i));
int ans = del;
for (int i = 0; i < n; ++i) { // 以下标i之后作为分割线
del += (s.charAt(i) - 'a') * 2 - 1; // 'a':-1,'b':1
ans = Math.min(del, ans);
}
return ans;
}
}
解法2 动态规划(1次遍历)
令
f
[
i
]
f[i]
f[i] 为使 s[0...i] 平衡的最少删除次数,考虑 s[0...i] 的最后一个字母:
- 如果它是
'b',则无需删除,问题规模缩小,变为「使s[0...i-1]平衡的最少删除次数」即 f [ i ] = f [ i − 1 ] f[i] = f[i-1] f[i]=f[i−1] ; - 如果它是
'a':- 删除它,则答案为「使
s[0...i-1]平衡的最少删除次数」加上1,即 f [ i − 1 ] + 1 f[i - 1] + 1 f[i−1]+1 。 - 保留它,则前面所有的
'b'都要删除。 - 令
bComeBeforeA为s[0...i]中'b'的个数,从而f[i] = min(f[i - 1] + 1, bComeBeforeA)。
- 删除它,则答案为「使
代码实现时,可以只用一个变量表示 f f f 。
class Solution {
public int minimumDeletions(String s) {
int bComeBeforeA = 0, minDel = 0;
for (int i = 0, n = s.length(); i < n; ++i) {
char c = s.charAt(i);
// c前面的字符串已经平衡,如果前面存在'b',则最后的'a'会失衡
if (c == 'a') {
// 要么删除前面的'b',要么删除这里的'a'
// 看哪种做法删除数最少
minDel = Math.min(bComeBeforeA, minDel + 1);
} else ++bComeBeforeA; // 最后一个字符是'b',不会导致失衡
}
return minDel;
}
}




![【GO】K8s 管理系统项目34[Docker方式–应用部署]](https://img-blog.csdnimg.cn/297a330b30f9404ca6a17ce95f15cfc8.png)














