LeetCode 面试题 17.05. 字母与数字
题目描述

题意分析
在数组中找一段连续子数组,使字母元素与数字元素的个数相等,并返回最长的一段。具体是哪个字母、数字大小是多少都不影响计数;长度相同时,要求返回左端点最早的片段。若没有符合条件的非空片段,返回空数组。
解法:前缀差值 + 哈希表
核心思路
[!blue]
把字母元素看作 $+1$、数字元素看作 $-1$,一段中两类数量相等,就等价于这段的和为 $0$。用
diff记录从数组开头到当前位置的“字母数减数字数”,不必真正改写输入数组。设下标
j和i处的前缀差值相同,则从j + 1到i的差值等于两个前缀相减,结果为 $0$。因此扫描到i时,只需查找之前是否出现过当前diff,就能定位一个以i为右端的平衡片段,其长度为i - j。用哈希表
first保存每个差值第一次出现的下标。对于固定右端i,差值相同的位置越早,片段越长;较晚的位置不可能给出更优长度,所以首次位置一旦记录就不覆盖。这样每个右端只需一次查表,而不必重新枚举所有左端。数组开始前的空前缀差值为 $0$,把它记在下标 $-1$:
first[0] = -1。当当前前缀本身已经平衡时,就能得到区间[0, i],长度也统一写成i - (-1)。最佳答案只在候选长度严格大于
bestLen时更新。因为右端i从小到大扫描,对于同样的长度,越早遇到的右端对应的左端也越早;等长时保留原答案,就满足最小左端要求。代码在差值首次出现时先登记当前位置,此时算出的长度为 $0$,不会误更新答案。最后按
bestStart和bestLen截取结果。若输入为空或没有平衡片段,bestLen保持 $0$,自然返回空数组。
解题步骤
- 初始化
first[0] = -1、diff = 0,最佳长度与起点都设为 $0$。- 扫描到下标
i时,字母令diff++,数字令diff--。- 若
diff尚未登记,保存下标i;否则保留其原有首次位置。- 令
j = first[diff],候选长度为i - j;仅当长度更大时,更新bestStart = j + 1和bestLen。- 返回从
bestStart开始、长度为bestLen的连续片段。
代码实现
class Solution {
// 记录每个前缀差值第一次出现的位置。
public String[] findLongestSubarray(String[] array) {
Map<Integer, Integer> first = new HashMap<>();
// 空前缀覆盖从数组首项开始的平衡片段。
first.put(0, -1);
int diff = 0;
int bestStart = 0;
int bestLen = 0;
for (int i = 0; i < array.length; i++) {
char ch = array[i].charAt(0);
if (Character.isDigit(ch)) {
diff--;
} else {
diff++;
}
// 同一前缀差只保留最早位置,当前区间才能最长。
if (!first.containsKey(diff)) {
first.put(diff, i);
}
int start = first.get(diff);
int len = i - start;
// 等长时不覆盖,保留起点更早的答案。
if (len > bestLen) {
bestLen = len;
bestStart = start + 1;
}
}
String[] res = new String[bestLen];
System.arraycopy(array, bestStart, res, 0, bestLen);
return res;
}
}
func findLongestSubarray(array []string) []string {
// 记录每个前缀差值第一次出现的位置。
first := make(map[int]int)
// 空前缀覆盖从数组首项开始的平衡片段。
first[0] = -1
diff := 0
bestStart := 0
bestLen := 0
for i := 0; i < len(array); i++ {
ch := array[i][0]
if ch >= '0' && ch <= '9' {
diff--
} else {
diff++
}
// 同一前缀差只保留最早位置,当前区间才能最长。
if _, ok := first[diff]; !ok {
first[diff] = i
}
start := first[diff]
length := i - start
// 等长时不覆盖,保留起点更早的答案。
if length > bestLen {
bestLen = length
bestStart = start + 1
}
}
return array[bestStart : bestStart+bestLen]
}
复杂度分析
- 时间复杂度:$O(n)$,哈希表操作按平均常数时间计。
- 空间复杂度:$O(n)$,前缀和首次位置记录。 Java 复制返回片段,Go 返回原数组的切片视图。
关键点总结
[!green]
- 相同前缀差值之间的区间贡献为零,正好表示两类元素等量。
- 保存最早前缀位置解决“最长”,按右端递增且仅严格增长时更新解决“左端最早”。
- 空前缀放在 $-1$,使从首项开始的区间也遵循相同长度公式。
易错点总结
[!yellow]
- 每次覆盖首次位置会缩短候选区间。
- 长度相等也覆盖答案,可能失去最早起点。
- 统计整段数组总数而不定位区间,不能得到最长连续片段。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 525. 连续数组 | 中等 | 把字母与数字元素映成正负1,等量区间等价于和为0,最长长度由最早前缀位置确定。 |
| 560. 和为 K 的子数组 | 中等 | 同样按前缀和相等定位零和区间,本题保存最早位置求最长,而非保存频次计数。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!