1 题目
题目:子数组之和(Subarray Sum)
描述:给定一个整数数组,找到和为 0 的子数组。你的代码应该返回满足要求的子数组的起始位置和结束位置,至少有一个子数组的和为 0。
lintcode题号——138,难度——easy
样例1:
输入: [-3, 1, 2, -3, 4]
输出: [0,2] 或 [1,3]
样例解释: 返回任意一段和为0的区间即可。
样例2:
输入: [-3, 1, -4, 2, -3, 4]
输出: [1,5]
2 解决方案
2.1 思路
使用暴力法可以在O(n^2)的时间复杂度内解决,考虑更优的方案,在一遍循环内完成,和为0的子数组可以看成是子数组起点的累加和与终点的累加和的值相等的子数组,在循环中一边将所有累加和计算出来,一边与之前的累加和对比,如有相等的,即为结果。
对数组的第一个元素需要做特殊处理,类似于在链表前加上dummyNode的操作。
2.2 图解
0 1 2 3 4 5
数组值:-3, 1, -4, 2, -3, 4
累加和:-3, -2, -6, -4, -7, -3
|<- 找到-3,相等 ->|
序号0 序号5
结果:左边序号是前缀和,需要+1指向本身,所以结果是[1,5],和为0的子数组为[1,-4,2,-3,4]。
如果结果是从数组头开始的子数组,需要特别处理:
0 1 2 3 4
数组值: -3, 1, 2, -5, 4
累加和: -3, -2, 0, -5, 1
此时无结果,但是很明显可以看出子数组[-3,1,2]和为零,被跳过了;
为了处理以原数组开头的子数组,可在数组前加上序号为-1的虚拟节点:
-1 0 1 2 3 4
数组值:null, -3, 1, 2, -5, 4
累加和: 0, -3, -2, 0, -5, 1
|<-找到0,相等->|
序号-1 序号2
结果:左边序号是前缀和,需要+1指向本身,所以结果是[0,1],和为0的子数组为[-3,1,2]。
2.3 时间复杂度
遍历整个数组的时间复杂度是O(n),在循环中包含了对map数据结构的插入和查找,map的底层是红黑树,插入和查找的时间复杂度都是O(log n),算法的总时间复杂度为O(n * log n)。
2.4 空间复杂度
使用了map数据结构,空间复杂度为O(n)。
3 源码
细节:
- 一边遍历一边存前缀和,并比较当前和与各个前缀和是否相等,相等则找到和为0的子数组了。
- 保存前缀和使用hashmap,采用<前缀和,序号>的方式,因为find只能针对key。
- map中预先插入(0,-1),表示前-1个数的和为零。
对数组头插入序号为-1的节点null,类似于在链表头建立一个dummyNode节点的操作,为了让头部与普通节点一样,规避对头部的特殊处理。
C++版本:
/**
* @param nums: A list of integers
* @return: A list of integers includes the index of the first number and the index of the last number
*/
vector<int> subarraySum(vector<int> &nums) {
// write your code here
vector<int> result;
if (nums.empty())
{
return result;
}
map<int, int> prefixSumMap;
prefixSumMap.insert({0, -1}); // 为了处理以数组头部开始的子串和为0的情况
int sum = 0;
for (int i = 0; i < nums.size(); i++)
{
sum += nums.at(i);
if (prefixSumMap.find(sum) != prefixSumMap.end())
{
result.push_back(prefixSumMap.at(sum) + 1); // 将sum对应的序号存入结果,注意序号的偏移
result.push_back(i);
return result;
}
prefixSumMap.insert({sum, i});
}
return result;
}