LintCode 138. 子数组之和

本文介绍了解决子数组和为0问题的三种方法,重点推荐第三种方法,利用哈希表记录前缀和及其对应的最早出现位置,以此快速判断是否存在和为0的子数组。这种方法的时间复杂度为O(n),空间复杂度也为O(n)。

思路:

方法一:最直观的想法是, 对每一个区间, 计算其中数值的和.若为0则返回此区间, 此方法复杂度为O(n^3).

方法二:方法一中进行了大量的重复计算, 故可以存储区间的计算值, 则此区间的父区间计算时不用再对此区间进行求值而可以直接对值进行使用.此方法复杂度为O(n^2)

方法三:记录每一位下标和相加到当前位的和的映射, 若和重复出现, 则相应的区间内的和必为0.

代码:

方法三:

vector<int> subarraySum(vector<int> &nums){
	// write your code here
	vector<int> result;
	map<int, int> ret;
	ret.insert(pair<int,int>(0, -1));
	int len = nums.size();
	int sum = 0;
	for (int i = 0; i < len; ++i){
		sum += nums[i];
		// 若sum的值重复出现, 则其中间的值定为0
		if (ret.find(sum) != ret.end()){
			result.push_back(ret[sum] + 1);
			result.push_back(i);
			return result;
		}
		ret[sum] = i;
	}
	return result;
}

致谢:

Subarray Sum

<think>嗯,用户让我介绍一下子数组之和的问题,也就是LintCode上的138题。题目是给定一个整数数组,找出和为0的子数组,返回起始和结束位置。而且要求使用哈希表的方法来实现。我得仔细想想怎么解决这个问题。 首先,我需要理解问题。子数组的和为0,也就是说,数组中存在某个连续的元素序列,它们的和等于0。比如例子中的数组{3,1,2,-3,4},子数组[1,2,-3]的和是0,所以起始位置1,结束位置3。或者可能还有其他的子数组,但只需要返回其中一个就行。 那怎么高效地找到这样的子数组呢?暴力解法是枚举所有可能的子数组,计算它们的和,这样时间复杂度是O(n²),对于大数组来说效率太低了。题目要求使用哈希表,所以应该考虑如何优化。 这时候想到前缀和的概念。前缀和数组prefix的第i项表示从数组开头到第i个元素的和。如果两个不同位置的前缀和相等,那么这两个位置之间的子数组的和就是0。比如,假设prefix[j] = prefix[i],那么i+1到j的子数组的和是0。 那如何利用这个性质呢?可以用一个哈希表来记录每个前缀和第一次出现的位置。遍历数组时,计算当前的前缀和,如果这个和在哈希表中已经存在,说明之前某个位置到当前位置的子数组和为0,直接返回那个位置的下一位置到当前位置的索引。如果遇到当前前缀和为0的情况,说明从开头到当前位置的和是0,直接返回0到当前索引。 举个例子,比如数组nums = [3,1,2,-3,4]。计算前缀和数组的话: prefix[0] = 3 prefix[1] = 3+1=4 prefix[2] = 4+2=6 prefix[3] = 6 + (-3) =3 → 这时候发现prefix[3]等于prefix[0],所以子数组是索引0+1=1到3,即子数组[1,2,-3],对应返回[1,3]。 那具体怎么实现呢?步骤如下: 1. 初始化哈希表,用来存储前缀和和对应的索引。初始时,可以放入一个0的前缀和,对应索引-1,这样当某个位置的前缀和为0时,可以直接得到从0到当前位置的子数组。 2. 遍历数组,计算当前的前缀和。 3. 检查当前前缀和是否存在于哈希表中: - 如果存在,取出对应的索引i,那么子数组是i+1到当前索引,返回即可。 - 如果不存在,将当前前缀和和索引存入哈希表。 4. 由于题目保证存在这样的子数组,所以循环中一定能找到结果。 需要注意边界情况,比如子数组从第一个元素开始的情况。例如数组[-1,1],前缀和第一次为-1,第二次为0。这时候哈希表中初始有0:-1,当第二次前缀和为0时,得到i=-1,所以子数组从0到1,返回[0,1]。 那代码的大致结构是: 创建一个哈希表,初始放入{0: -1}。 初始化前缀和为0。 遍历数组,每次加上当前元素得到新的前缀和。 如果这个前缀和已经在哈希表中存在,返回哈希表中的值+1到当前索引。 否则,将前缀和和当前索引存入哈希表。 这样时间复杂度是O(n),空间复杂度也是O(n),因为哈希表存储前缀和。 测试一下例子是否正确。例如nums = [3,1,2,-3,4]: 遍历过程: i=0,num=3,前缀和为3。哈希表中没有3,存入{0:-1,3:0}。 i=1,num=1,前缀和4,不存在,存入。 i=2,num=2,前缀和6,存入。 i=3,num=-3,前缀和3。此时哈希表中有3对应的0。所以返回[0+1,3],即[1,3],正确。 另一个例子,nums = [-1,1],初始哈希表有0:-1。 i=0,前缀和-1,不存在,存入。 i=1,前缀和0。哈希表存在0,对应的-1,返回[-1+1,1]即[0,1]。 那这样的逻辑应该正确。那接下来按照这个思路写代码的话,需要注意索引的处理。因为题目中的起始和结束位置是否是闭区间?比如例子中的答案给出的是(0,2)或者(1,3)。假设这里的结束位置是闭区间,比如在例子中,子数组是索引1到3(包含3),那么对应的起始是1,结束是3。所以在代码中,当找到前缀和重复时,返回的是map[sum] +1到当前i。 所以在代码中,当发现当前前缀和存在于哈希表,那么返回的起始位置是哈希表里存的索引+1,结束位置是当前的i。 这样应该没问题。那代码的结构大致是: vector<int> subarraySum(vector<int> &nums) { unordered_map<int, int> hash; hash[0] = -1; int sum = 0; for (int i = 0; i < nums.size(); ++i) { sum += nums[i]; if (hash.find(sum) != hash.end()) { return {hash[sum] + 1, i}; } hash[sum] = i; } return {}; // 题目保证有解,所以这里不会执行 } 这样应该可以解决问题。需要注意的是,哈希表的初始化要放入0:-1,这样可以处理前缀和直接为0的情况。例如数组前几个元素和为0,如nums=[-3,1,2],那么sum在i=0时是-3,i=1时是-2,i=2时是0。这时候hash中0存在,对应的-1,返回-1+1=0到2,即[0,2],正确。 可能的问题:是否有其他情况需要考虑?比如子数组长度为0?但题目保证至少有一个解,所以无需考虑。另外,是否会有多个重复的前缀和?比如多个位置的前缀和相同,但哈希表只记录第一个出现的,这样后面的位置遇到同样的前缀和时,可以找到最短的子数组吗?不,题目只需要返回任意一个即可,所以没问题。 综上,这个方法是正确的。</think>### 子数组之和为0的解法详解 要解决**和为0的子数组**问题,我们可以利用**前缀和**和**哈希表**来高效地找到符合条件的子数组。以下是详细的解决思路和代码实现: --- #### **核心思路** 1. **前缀和概念**: - 前缀和 `prefix_sum[i]` 表示数组前 `i` 个元素的和。 - 若存在两个不同的下标 `i` 和 `j` 使得 `prefix_sum[i] = prefix_sum[j]`,则子数组 `nums[i+1...j]` 的和为0。 2. **哈希表的作用**: - 记录每个前缀和**第一次出现的位置**。 - 当某个前缀和重复出现时,说明两次出现之间的子数组和为0。 --- #### **算法步骤** 1. **初始化哈希表**: - 哈希表初始值:`{0: -1}`,表示前缀和为0时,下标为-1(处理从数组开头开始的子数组和为0的情况)。 2. **遍历数组计算前缀和**: - 逐个累加元素,计算当前前缀和。 - 若当前前缀和已在哈希表中,则直接返回子数组的起始和结束下标。 - 否则,将当前前缀和及其下标存入哈希表。 --- #### **代码实现** ```cpp #include <vector> #include <unordered_map> using namespace std; vector<int> subarraySum(vector<int> &nums) { unordered_map<int, int> prefix_map; // 哈希表:key为前缀和,value为下标 prefix_map[0] = -1; // 初始值,处理前缀和为0的子数组 int prefix_sum = 0; for (int i = 0; i < nums.size(); i++) { prefix_sum += nums[i]; // 计算当前前缀和 // 若前缀和已存在,说明找到了和为0的子数组 if (prefix_map.find(prefix_sum) != prefix_map.end()) { return {prefix_map[prefix_sum] + 1, i}; } // 记录前缀和的首次出现位置 prefix_map[prefix_sum] = i; } return {}; // 题目保证有解,此处不会执行 } ``` --- #### **示例分析** 以输入 `nums = {3, 1, 2, -3, 4}` 为例: 1. **前缀和计算**: - i=0: `sum=3` → 存入哈希表。 - i=1: `sum=4` → 存入。 - i=2: `sum=6` → 存入。 - i=3: `sum=3` → 发现与i=0的sum相同 → 子数组为[1,3](即元素1,2,-3)。 2. **输出结果**: - 返回 `{1, 3}`。 --- #### **复杂度分析** - **时间复杂度**:$O(n)$,只需一次遍历。 - **空间复杂度**:$O(n)$,哈希表存储最多n个前缀和。 --- #### **关键点总结** - **前缀和哈希表**:通过记录前缀和的首次出现位置,快速定位子数组。 - **边界处理**:初始哈希表值 `{0: -1}` 处理从数组开头开始的子数组和为0的情况。 此方法高效且直观,能有效解决子数组和为0的问题。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值