题目描述
给定一个数组 arr,其中包含正整数和负整数,任务是计算总和为 0 的最长子数组的长度。
示例:
输入: arr[] = [15, -2, 2, -8, 1, 7, 10, 23]
输出: 5
解释: 总和为 0 的最长子数组是 [-2, 2, -8, 1, 7]。
思路
前缀和+哈希的思路。
前缀和是什么?
对于一个数组nums,前缀和的定义为:
prefix[i] = nums[0] + nums[1] + nums[2] + ... + nums[i]。
举例,对于数组
[15, -2, 2, -8, 1, 7, 10, 23],其前缀和为 [15 ,13, 15, 7,8,15,10,33]。
通过计算前缀和,可以快速确定某段连续数组的和是否为0,比如坐标为0和坐标为2的前缀和差为:
prefix[2] - prefix[0] = 0
说明(0,2]区间上连续子数组的和为0。
[15, -2, 2, -8, 1, 7, 10, 23]
进一步,可以通过维护一个hash,hash的key为前缀和,hash的value为坐标。
算法步骤:
- 遍历数组,如果前缀和为0,说明从开始到当前位置的连续子数组和为0。
- 如果前缀和已经存在于哈希表中,则说明从哈希表记录的索引到当前位置的子数组和为 0。
实现
java
static int maxLen(int arr[]) {
// 创建哈希表存储前缀和及其对应的索引
HashMap<Integer, Integer> prefixMap = new HashMap<>();
int maxLength = 0;
int prefixSum = 0;
for (int i = 0; i < arr.length; i++) {
prefixSum += arr[i]; // 计算前缀和
// 如果前缀和为 0,则从开始到当前位置的子数组和为 0
if (prefixSum == 0) {
maxLength = i + 1;
}
// 如果前缀和已经存在于哈希表中
if (prefixMap.containsKey(prefixSum)) {
// 计算子数组长度并更新最大长度
maxLength = Math.max(maxLength, i - prefixMap.get(prefixSum));
} else {
// 如果前缀和第一次出现,存储其索引
prefixMap.put(prefixSum, i);
}
}
return maxLength;
}
c++
class Solution {
public:
int maxLen(vector<int>& arr) {
unordered_map<int, int> hash; // key 前缀和 value 前缀和的坐标
int prefix = 0;
int ans = 0;
for (int i = 0;i < arr.size();i++) {
prefix = prefix + arr[i];
if (prefix == 0)
{
// 说明从数组的起始位置遍历到现在都是0
ans = i + 1;
}
if (hash.find(prefix) != hash.end()) {
ans = max(ans,i - hash[prefix]);
}
else
{
hash[prefix] = i;
}
}
return ans;
}
};
python3
class Solution:
def maxLen(self, arr):
prefix = 0
ans = 0
prefix_map = {}
for index,val in enumerate(arr):
prefix = prefix + val
if prefix == 0:
ans = index + 1
if prefix in prefix_map:
ans = max(ans,index- prefix_map[prefix])
else:
prefix_map[prefix] = index
return ans
复杂度分析
时间复杂度为O(N),只需要遍历一遍数组。
空间复杂度O(N),需要维护一个哈希数组。