一.题目
二.思路讲解
第一步:转化问题
题目要求找到含有相同数量 0 和 1 的最长连续子数组。直接分别统计 0 和 1 的个数比较麻烦,我们可以通过一个巧妙的转化:将数组中的 0 全部替换为 -1 ,那么原问题就等价于寻找和为 0 的最长连续子数组。因为当子数组中 0 和 1 数量相等时,-1 和 1 的总和恰好为 0。这样,我们就将问题转化为了和为 0 的最长子数组问题,与之前的"和为 k 的子数组"类似,但这里要求的是最长长度,而不是个数。
第二步:使用前缀和 + 哈希表记录最早出现位置
我们采用前缀和 的思想,定义前缀和 sum 为从数组起始到当前位置的累加和(其中 0 视为 -1)。那么对于任意子数组 [j+1, i] ,其和为 sum[i] - sum[j] 。我们需要这个和为 0,即 sum[i] == sum[j]。因此,问题转化为:找到两个相同的前缀和,使得它们之间的距离最大。
为了高效求解,我们使用一个哈希表,key 为前缀和的值,value 为该前缀和第一次出现的下标 。遍历数组时,计算当前前缀和 sum:
-
如果哈希表中已经存在该前缀和,说明之前出现过相同的前缀和,那么从之前那个位置的下一个元素到当前位置的子数组和为 0,其长度为 i - hash[sum](注意:hash[sum] 记录的是第一次出现该前缀和的下标)。我们更新最长长度。
-
如果哈希表中不存在该前缀和,则将其加入哈希表,记录当前下标作为第一次出现的位置。
初始化:为了处理从数组开头开始的子数组,我们需要在哈希表中预先存入 hash[0] = -1,表示前缀和为 0 在下标 -1 处已经出现(即空数组的情况)。这样,当第一次遇到前缀和为 0 时,其长度就是当前下标加 1,正确计算。
要点总结
-
转化关键:将 0 变为 -1,使问题转化为求和为 0 的最长子数组。
-
哈希表存储内容 :不再是出现次数,而是第一次出现该前缀和的下标,这样才能计算最长距离。
-
初始化 :
hash[0] = -1是处理从起点开始的子数组的必要条件。
三.代码演示
cpp
class Solution {
public:
int findMaxLength(vector<int>& nums)
{
unordered_map<int,int> hash;
//把0变成-1
for(auto& x:nums)
{
if(x == 0)
x = -1;
}
hash[0] = -1;//下标
int sum = 0;
int ret = 0;
for(int i = 0;i < nums.size();i++)
{
sum += nums[i];
//这个前缀之前和存不存在
if(hash.count(sum))
ret = max(ret,i - hash[sum]);//存在计算长度
else
hash[sum] = i;//不存在更新下标
}
return ret;
}
};
四.代码讲解
一、数据预处理与哈希表初始化
为了将问题转化为求和为 0 的最长连续子数组,我们首先对原数组进行预处理:将所有的 0 替换为 -1 ,而 1 保持不变。这样,原数组中 0 和 1 数量相等的子数组,其累加和必然为 0。
接下来,我们需要一个哈希表(unordered_map<int, int>)来记录每个前缀和第一次出现的下标。这里与之前统计次数的题目不同,因为我们要求的是最长长度,需要知道最早出现相同前缀和的位置,从而计算出当前下标与该位置的差值。
初始化哈希表时,我们预先存入 hash[0] = -1。这个初始值的含义是:在下标 -1 (即数组起始之前)处,前缀和为 0 已经出现过一次。这样做是为了正确处理从数组开头开始的子数组------当第一次遇到前缀和为 0 时,当前下标与 -1 的差值就是当前下标加 1,正好等于该子数组的长度。
二、遍历数组并更新最长长度
定义变量 sum 用于累加当前前缀和(初始为 0),ret 用于记录当前找到的最长长度(初始为 0)。然后从左到右遍历数组的每一个元素(下标 i 从 0 到 n-1):
-
更新当前前缀和 :将当前元素的值(已转化为 -1 或 1)加到
sum上,得到从数组起始到当前位置的前缀和。 -
检查哈希表 :在哈希表中查找键
sum。如果已经存在,**说明之前出现过相同的前缀和,那么从之前那个位置的下一个元素到当前位置所形成的子数组,其和为 0。**当前长度即为i - hash[sum],用这个长度更新ret(取较大值)。注意,这里不需要将当前下标再存入哈希表,因为我们只保留第一次出现的位置,这样才能保证得到的长度是最大的。 -
如果哈希表中不存在键
sum,说明这个前缀和是第一次出现,那么将当前下标i存入哈希表,作为该前缀和的最早出现位置。
三、关键细节
-
为什么要将 0 变成 -1:这样原问题"0 和 1 数量相等"就等价于"累加和为 0",从而可以利用前缀和与哈希表求解。
-
哈希表存储的是最早出现下标:因为我们需要最长长度,所以只保留第一次出现的位置,之后遇到相同的前缀和时,用当前下标减去最早下标即可得到最长的跨度。
-
初始化
hash[0] = -1的意义 :假设整个数组从开头到某个位置 i 的和为 0,那么子数组 [0, i] 本身就是一个合法解,其长度为 i+1。这个长度可以通过i - (-1)得到。没有这个初始化,当第一次遇到前缀和为 0 时,哈希表中没有记录,就会漏掉这种从起点开始的子数组
