晚上刷题刷到一道新题,LeetCode 3713「最长的平衡子串 I」,难度标的是中等。这道题看起来短小精悍,但里面藏着一个很典型的算法套路:前缀和 + 哈希表。如果你刷过 LC 525(连续数组)、LC 1371(每个元音包含偶数次的最长子字符串),会对这个套路相当眼熟。这篇文章我把完整思考过程拆开揉碎讲清楚,从暴力解法到 O(n) 最优解,还有那一堆让人莫名其妙的边界条件,一次性说明白。
1. 先别急着写代码:把「平衡子串」翻译成人话
1.1 为什么「平衡」就是两种字符数量相等
题目本身很短,核心就是这句话:给定一个由 0 和 1 组成的字符串 s,找出最长的连续子串,使得子串中 0 和 1 的数量相等。这种子串就叫平衡子串。
举个例子,s = "00110":
- 子串 "00":两个 0,零个 1,不平衡。
- 子串 "011":一个 0,两个 1,不平衡。
- 子串 "01":一个 0,一个 1,平衡,长度 2。
- 子串 "0110":两个 0,两个 1,平衡,长度 4。
- 子串 "0011":两个 0,两个 1,平衡,长度 4。
所以最长平衡子串长度为 4。整个字符串呢?三个 0、两个 1,不平衡,因为数量不相等。
如果你再观察一下,会发现一个隐藏规律:既然 0 和 1 数量相等,那平衡子串的总长度一定是偶数。这一条看起来像是废话,但后面写代码时可以拿来做剪枝,也可以用来提前检查答案的合理性。
1.2 这道题适合谁来练
说实话,这道题你说它难,它没有复杂的递归也没有树和图,但你说它简单,第一次碰到前缀和思路的人,可能要在暴力的坑里卡很久。
我个人的判断:这道题非常适合用来补「子数组问题」的短板。你已经能熟练写出滑动窗口解决那种"窗口内满足某条件"的题,但会发现滑动窗口在这里并不好用,因为窗口收缩条件没法简单定义——0 和 1 的数量差是动态变化的,你不清楚该缩左边还是缩右边。这时候就需要换一个切入点:前缀和。这是从「暴力枚举」到「数学等价」的典型转型练习,值得花时间吃透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法的天花板:O(n^2) 也扛不住的数据规模
2.1 从双层循环出发,先写一个能算对的版本
遇到子串问题,第一反应通常是枚举左右端点。最粗暴的写法是三层循环:枚举起点 l,枚举终点 r,然后统计 s[l..r] 中 0 和 1 的数量。这个写法复杂度 O(n^3),一般只用来对拍验证。
稍微优化一下,枚举 l 和 r 时顺带更新计数,能把复杂度降到 O(n^2):
python复制def brute_force(s: str) -> int:
n = len(s)
ans = 0
for l in range(n):
cnt0 = cnt1 = 0
for r in range(l, n):
if s[r] == '0':
cnt0 += 1
else:
cnt1 += 1
if cnt0 == cnt1:
ans = max(ans, r - l + 1)
return ans
这段代码逻辑完全正确。对于超短字符串,比如长度只有几十,结果也完全没问题。
2.2 为什么这个复杂度注定过不了
问题在于数据规模。力扣中等题,字符串长度通常到 10^5 这个量级。O(n^2) 意味着需要执行大约 10^10
