2.3 累加数组数字求子数组之和
使用双指针求子数组之和的前提是元素均为正整数, 否则添加或删除元素并不能保证和的减小或增大.
前缀和:
对于数组 [], 从第一个到第 k 个的元素之和记为 .
那么子数组 [] 的和可以表示为 =
2.3.1 问题10: 和为 k 的子数组
输入: 整数数组和整数k
输出: 子数组之和为k的子数组个数
例: [1 1 1]和2, [1 1] [1 1] 满足条件, 输出2
2.3.1.1 分析&题解
由于数组元素不一定是正整数, 那么不能使用双指针法.
暴力解法 TC: O()
计算每个子数组的和, 寻找子数组时间为 O() 为计算子数组之和时间O(n), 总体时间为 O()
func subarraySumBruteForce(nums []int, k int) int {
	count := 0
	for i := 0; i < len(nums); i++ {
		for j := i; j < len(nums); j++ {
			sum := 0
			for k := i; k <= j; k++ {
				sum += nums[k]
			}
			if sum == k {
				count++
			}
		}
	}
	return count
}
暴力解法 - 优化 TC: O()
计算数组元素和时, 将前一个子数组元素和 记录下来, 那么下一个数组元素和即为
时间复杂度优化为 O()
func subarraySumBruteForceOptimized(nums []int, k int) int {
	count := 0
	for i := 0; i < len(nums); i++ {
		sum := 0
		for j := i; j < len(nums); j++ {
			sum += nums[j]
			if sum == k {
				count++
			}
		}
	}
	return count
}
HashTable TC: O(n) SC: O(n)
每次将前 i 个元素之和 记录下来, 那么对于子数组 若和 , 则有 , 则
那么问题就转化为寻找满足条件的 j 的个数:
- 构建哈希表, Key: 前 i 个元素和, Val: 和出现的次数
 - 遍历数组: 
- 计算前 i 个元素和 S
 - 统计 Key 为 S-k 的次数,
 - 存入当前结果S .
 
 - 返回结果
 
func subarraySumHashTable(nums []int, k int) int {
	m := make(map[int]int, len(nums))
	count := 0
	// 当前子数组满足条件,故次数为1
	m[0] = 1
	sum := 0
	for _, v := range nums {
		sum += v
		if val, ok := m[sum-k]; ok {
			count += val
		}
		m[sum] += 1
	}
	return count
}
注意: m[0] 表示当前子数组就是满足条件的, 故出现的次数 m[0] = 1
2.3.2 问题11: 0和1个数相同的子数组
输入: 只包含0和1的数组
输出: 0和1个数相同的最长连续子数组的长度
2.3.2.1 分析
因为数组只包含0和1, 可以将0全部换成-1. 那么问题就变成了 -1 和 1的数目相同, 此时子数组的和必定为0.
问题转换成了求子数组之和为0的最大长度.
2.3.2.2 题解
- 构造哈希表, key: 前缀和(前
i个元素之和), value: 当前下标i - 遍历数组: 
- 将0换成 -1, 计算前
i项和sum - 在哈希表中寻找 
sum-k(k = 0)的下标j, 计算长度i - (j + 1) +1即i - j, 并更新最大长度 - 将
sum存入哈希表 
 - 将0换成 -1, 计算前
 - 返回最大长度
 
func findMaxLength(nums []int) int {
	maxLen := 0
	m := make(map[int]int, len(nums))
    // 满足条件的当前子数组的下标 j, 用于计算长度
    // 长度: i-0+1 = i - (j+1) +1 => j = -1
	m[0] = -1
	sum := 0
	for i, n := range nums {
		if n == 0 {
			n = -1
		}
		sum += n
		if j, ok := m[sum]; ok {
            // nums[i] 到 nums[j+1]的长度
			maxLen = max(maxLen, i-j)
		} else {
			// 第一次和为 sum 的下标
			m[sum] = i
		}
	}
	return maxLen
}
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
- m[0] 表示当前子数组满足条件, 那么长度为 i + 1, 所以设置下标 m[0] = -1
 - 因为要记录的是下标, 所以只能记录第一次和为sum的下标i, 故 m[sum] = i
 
2.3.3 问题12: 左右两边数组和相等
输入整数数组.
- 若一个数字左边子数组的和与右边子数组的和相同, 返回下标
 - 若存在多个下标, 返回最左边的
 - 不存在则返回 -1
 
2.3.3.1 分析
扫描数组, 到第i个数字时, 和 ,那么左边的子数组之和为 
右边子数组之和 , 为所有元素之和.
所以先计算一次所有元素之和, 在扫描一次数组即可.
2.3.3.2 题解
- 扫描数组, 计算总和 total
 - 扫描数组: 
- 计算当前和
 - 左边和为 , 右边和为
 - 比较两边返回结果
 
 
由于是从左到右扫描数组, 故返回值一定是最左边的
func pivotIndex(nums []int) int {
	total := 0
	for i := range nums {
		total += nums[i]
	}
	sum := 0
	for i := range nums {
		sum += nums[i]
		if (sum - nums[i]) == (total - sum) {
			return i
		}
	}
	return -1
}
2.3.4 问题13: 二维子矩阵的数字之和
输入二维矩阵, 计算给定左上角坐标到右下角坐标的子矩阵的数字之和.
计算数字之和的函数将被调用多次.
2.3.4.1 分析
若计算函数直接进行计算, 那么时间复杂度为 O(mn), 又因计算函数将被调用多次, 此时效率将大大降低.
因此, 需要引入辅助矩阵来将子矩阵之和缓存起来, 调用计算函数时可直接使用缓存数据以提高效率.
对于矩阵m, 假设坐标(0, 0)到(r, c) 的子矩阵之和记为.
那么坐标到的子矩阵之和 可以由以下方式计算:
那么可以构造辅助矩阵 sums, 扫描原矩阵将计算结果存入辅助矩阵.
实际上也是利用前缀和的方式, 这次是二维的前缀和
2.3.4.2 题解
type NumMatrix struct {
	sums [][]int
}
func Constructor(matrix [][]int) NumMatrix {
	m := len(matrix)
	if m == 0 {
		return NumMatrix{}
	}
	n := len(matrix[0])
	sums := make([][]int, m+1)
	sums[0] = make([]int, n+1)
	for r := range matrix {
		sums[r+1] = make([]int, n+1)
		for c := range matrix[r] {
			sums[r+1][c+1] = sums[r+1][c] + sums[r][c+1] - sums[r][c] + matrix[r][c]
		}
	}
	return NumMatrix{sums: sums}
}
func (this *NumMatrix) SumRegion(row1 int, col1 int, row2 int, col2 int) int {
	sums := this.sums
	return sums[row2+1][col2+1] - sums[row2+1][col1] - sums[row1][col2+1] + sums[row1][col1]
}
- 辅助矩阵比原矩阵多一行和列, 可以避免判断参数坐标为0(导致下标为-1)的情况
 
