lintcode 138. 子数组之和

本文介绍了一种高效算法,用于在整数数组中找到和为零的子数组,并给出了具体的实现代码。通过计算数组的前缀和并利用哈希表记录已遇到的和,该算法能在O(n)时间内解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述

给定一个整数数组,找到和为零的子数组。你的代码应该返回满足要求的子数组的起始位置和结束位置

样例

给出 [-3, 1, 2, -3, 4],返回[0, 2] 或者 [1, 3]

问题分析:

这个问题比较好想的算法的时间复杂度是O(n^2),这不是我们希望看到的。

要解决这个问题我觉得需要明白两点:

1.一个子数组的和为零,我们假设这个子数组是{ a[i], a[i+1], ..., a[j] },也就是a[i] + a[i+1] +...+ a[j] = 0,起始和结束位置其实就是i和j。那么 a[0] + a[1] +...+ a[i-1] + a[i] + a[i+1] +...+ a[j]a[0] + a[1] +...+ a[i-1] 。把这个式子转换下,假如这个数组的前 i 项的和与前j项的和相等,那么i项到j项之间的数组的元素的和肯定是零。i,j就是需要的结果。

所以这个问题就转换成了求数组前n项和的问题

2.假如前 i 项的和为零,那么0 和 i 即为所求结果

我提供一种O(n)的解法,希望对大家有所启发

public List<Integer> subarraySum(int[] nums) {
        // write your code here
        ArrayList<Integer> list = new ArrayList<>();
		int[] sumArrays = new int[nums.length];
		sumArrays[0] = nums[0]; 
		for (int i = 1; i < nums.length; i++) {
			sumArrays[i] = sumArrays[i-1] + nums[i]; 
		}
		HashMap<Integer, Integer> indexsMap = new HashMap<>();
		for (int i = 0; i < sumArrays.length; i++) {
		    if (sumArrays[i] == 0) {
				list.add(0);
				list.add(i);
				return list;
			}
			if (indexsMap.containsKey(sumArrays[i])) {
				list.add(indexsMap.get(sumArrays[i])+1);
				list.add(i);
				return list;
			}
			indexsMap.put(sumArrays[i], i);
		}
		return list;
    }





<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的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值