每日算法 - 20240605
525. 连续数组
题目描述
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

思路
前缀和 + 哈希表
解题过程
核心思想是将问题巧妙地转换为寻找和为特定值的子数组问题。
-
转换问题:我们将数组中的
0视为-1,1视为1。这样,如果一个子数组中0和1的数量相等,那么这个子数组(转换后)的和就为0。问题就变成了“找到和为0的最长连续子数组的长度”。 -
前缀和:我们定义
prefix[i]为原数组从0到i-1经过转换后的元素之和。- 如果子数组
nums[j...k](转换后) 的和为0,那么prefix[k+1] - prefix[j] = 0,即prefix[k+1] == prefix[j]。
- 如果子数组
-
哈希表优化:
- 我们使用一个哈希表
map来存储特定前缀和首次出现的索引。map的键是前缀和的值,值是该前缀和第一次出现时的索引i。 - 初始化时,我们放入
map.put(0, -1)。这表示和为0的前缀“出现”在索引-1处,这样做是为了方便计算当一个从索引0开始的子数组本身和为0时的长度(即i - (-1) = i + 1)。 - 遍历数组,计算当前的前缀和
current_prefix。- 如果在
map中已经存在current_prefix,假设它之前出现的索引是prev_index = map.get(current_prefix),那么从prev_index + 1到当前索引i的子数组(转换后)的和就是0。其长度为i - prev_index。我们用这个长度更新最大长度max。 - 如果
map中不存在current_prefix,则将其存入map:map.put(current_prefix, i)。我们只记录第一次出现的位置,因为这样能保证在后续找到相同前缀和时,计算出的子数组长度是最大的。
- 如果在
- 我们使用一个哈希表
复杂度
- 时间复杂度: O ( N ) O(N) O(N),其中 N 是数组的长度。我们只遍历数组一次。
- 空间复杂度: O ( N ) O(N) O(N),在最坏的情况下,哈希表可能存储 N 个不同的前缀和。
Code
class Solution {
public int findMaxLength(int[] nums) {
int max = 0, n = nums.length;
Map<Integer, Integer> map = new HashMap<>(n);
map.put(0, -1);
int prefix = 0;
for (int i = 0; i < n; i++) {
prefix += (nums[i] == 0 ? -1 : 1);
if (map.containsKey(prefix)) {
max = Math.max(max, (i - map.get(prefix)));
} else {
map.put(prefix, i);
}
}
return max;
}
}
面试题 17.05. 字母与数字
题目描述
给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。返回该子数组。若存在多个最长子数组,返回最靠左的。

思路
前缀和 + 哈希表
这道题与上一题的思路非常相似。
解题过程
-
转换问题:我们将数组中的字母视为
1(或-1),数字视为-1(或1,只要两者相反即可)。例如,字母计为+1,数字计为-1。如果一个子数组中字母和数字的数量相同,那么这个子数组(转换后)的和就为0。问题就变成了“找到和为0的最长连续子数组”。 -
前缀和与哈希表:
- 同样使用
prefixSum记录从数组开头到当前位置i-1的转换后元素之和。 - 使用哈希表
map存储<前缀和, 首次出现的索引>。 - 初始化
map.put(0, -1)。 - 遍历数组,计算当前的前缀和
current_prefixSum。- 如果
map中已存在current_prefixSum,设其之前出现的索引为prev_index = map.get(current_prefixSum)。这意味着从prev_index + 1到当前索引i的子数组(转换后)的和为0。其长度为current_length = i - prev_index。 - 如果
current_length大于已记录的最大长度maxLen,则更新maxLen和最长子数组的起始索引startIndex = prev_index + 1。 - 如果
map中不存在current_prefixSum,则将其存入map:map.put(current_prefixSum, i)。
- 如果
- 同样使用
-
返回结果:根据记录的
startIndex和maxLen,从原数组中截取并返回结果。由于题目要求“若存在多个最长子数组,返回最靠左的”,我们只在第一次遇到前缀和时记录索引,并且在current_length > maxLen时才更新,这样自然保证了最靠左的特性。
复杂度
- 时间复杂度: O ( N ) O(N) O(N),其中 N 是数组的长度。
- 空间复杂度: O ( N ) O(N) O(N),哈希表可能存储 N 个不同的前缀和。
Code
class Solution {
public String[] findLongestSubarray(String[] array) {
int maxLen = 0, n = array.length;
Map<Integer, Integer> map = new HashMap<>(n);
map.put(0, -1);
int prefix = 0, index = 0;
for (int i = 0; i < n; i++) {
prefix += (isNum(array[i]) ? -1 : 1);
if (map.containsKey(prefix)) {
int preIndex = map.get(prefix);
if (i - preIndex > maxLen) {
index = preIndex + 1;
maxLen = i - preIndex;
}
} else {
map.put(prefix, i);
}
}
String[] s = new String[maxLen];
System.arraycopy(array, index, s, 0, maxLen);
return s;
}
private boolean isNum(String s) {
return (s.charAt(0) >= '0' && s.charAt(0) <= '9');
}
}


















